diff options
| author | Bogdan Drutu <bdrutu@google.com> | 2017-06-06 13:56:55 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-06-06 13:56:55 -0700 |
| commit | 80ec0c120bb133b3d3747406be7e4c543b9e0446 (patch) | |
| tree | 94d30b9612fd44408026455597ac7c76492536b2 /api | |
| parent | 84d0f46e88faa89c0d822a73a7a610b9ac1bc471 (diff) | |
| download | platform_external_opencensus-java-80ec0c120bb133b3d3747406be7e4c543b9e0446.tar.gz platform_external_opencensus-java-80ec0c120bb133b3d3747406be7e4c543b9e0446.tar.bz2 platform_external_opencensus-java-80ec0c120bb133b3d3747406be7e4c543b9e0446.zip | |
Move internal & common & trace to the new package io.opencensus (#339)
Diffstat (limited to 'api')
72 files changed, 7948 insertions, 0 deletions
diff --git a/api/README.md b/api/README.md new file mode 100644 index 00000000..797ebe0a --- /dev/null +++ b/api/README.md @@ -0,0 +1,6 @@ +OpenCensus API +====================================================== + +* Java 6 and Android compatible. +* The abstract classes in this directory can be subclassed to create alternative + implementations of the Instrumentation library. diff --git a/api/build.gradle b/api/build.gradle new file mode 100644 index 00000000..7cfb33b2 --- /dev/null +++ b/api/build.gradle @@ -0,0 +1,11 @@ +description = 'OpenCensus: API' + +dependencies { + compile libraries.grpc_context, + libraries.guava + compileOnly libraries.auto_value + + signature "org.codehaus.mojo.signature:java16:+@signature" +} + +javadoc.exclude 'io/opencensus/**'
\ No newline at end of file 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..420c5191 --- /dev/null +++ b/api/src/main/java/io/opencensus/common/Clock.java @@ -0,0 +1,34 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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. */ +public abstract class Clock { + + /** + * Obtains the current instant from this clock. + * + * @return the current instant. + */ + 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. + */ + 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..c0e9f1bc --- /dev/null +++ b/api/src/main/java/io/opencensus/common/Duration.java @@ -0,0 +1,103 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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; + +/** + * 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. + */ +public class 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 {@link Duration} with specified fields. For invalid inputs, a {@link Duration} of + * zero is returned. + */ + public static Duration create(long seconds, int nanos) { + if (seconds < -MAX_SECONDS || seconds > MAX_SECONDS) { + return new Duration(0, 0); + } + if (nanos < -MAX_NANOS || nanos > MAX_NANOS) { + return new Duration(0, 0); + } + if ((seconds < 0 && nanos > 0) || (seconds > 0 && nanos < 0)) { + return new Duration(0, 0); + } + return new Duration(seconds, nanos); + } + + /** Creates a new {@link Duration} from given milliseconds. */ + public static Duration fromMillis(long millis) { + long seconds = millis / NUM_MILLIS_PER_SECOND; + int nanos = (int) (millis % NUM_MILLIS_PER_SECOND) * NUM_NANOS_PER_MILLI; + return new Duration(seconds, nanos); + } + + /** Returns the number of seconds in the {@link Duration}. */ + public long getSeconds() { + return seconds; + } + + /** Returns the number of nanoseconds in the {@link Duration}. */ + public int getNanos() { + return nanos; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof Duration)) { + return false; + } + + Duration that = (Duration) obj; + return seconds == that.seconds && nanos == that.nanos; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (int) (seconds ^ (seconds >>> 32)); + result = 31 * result + nanos; + return result; + } + + @Override + public String toString() { + return "Duration<" + seconds + "," + nanos + ">"; + } + + private static final long MAX_SECONDS = 315576000000L; + private static final int MAX_NANOS = 999999999; + private static final long NUM_MILLIS_PER_SECOND = 1000L; + private static final int NUM_NANOS_PER_MILLI = 1000000; + private final long seconds; + private final int nanos; + + private Duration(long seconds, int nanos) { + this.seconds = seconds; + this.nanos = nanos; + } +} 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..51bf845c --- /dev/null +++ b/api/src/main/java/io/opencensus/common/Function.java @@ -0,0 +1,25 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.stats.ViewDescriptor} for an example of it's 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. + */ +public interface Function<A, B> { + B apply(A arg); +} 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..9d540e21 --- /dev/null +++ b/api/src/main/java/io/opencensus/common/Internal.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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. + */ +@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..1dac89f8 --- /dev/null +++ b/api/src/main/java/io/opencensus/common/NonThrowingCloseable.java @@ -0,0 +1,35 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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> + */ +public interface NonThrowingCloseable extends Closeable { + @Override + void close(); +} 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..34c4c851 --- /dev/null +++ b/api/src/main/java/io/opencensus/common/Timestamp.java @@ -0,0 +1,146 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 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). + */ +@Immutable +public final class Timestamp { + private static final long MAX_SECONDS = 315576000000L; + private static final int MAX_NANOS = 999999999; + private static final long NUM_MILLIS_PER_SECOND = 1000L; + private static final int NUM_NANOS_PER_MILLI = 1000 * 1000; + private static final long NUM_NANOS_PER_SECOND = NUM_NANOS_PER_MILLI * NUM_MILLIS_PER_SECOND; + private final long seconds; + private final int nanos; + + private Timestamp(long seconds, int nanos) { + this.seconds = seconds; + this.nanos = nanos; + } + + // TODO(bdrutu): Make create and fromMillis package-protected. + + /** + * 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 {@link Timestamp} with specified fields. For invalid inputs, a {@link Timestamp} of + * zero is returned. + */ + public static Timestamp create(long seconds, int nanos) { + if (seconds < -MAX_SECONDS || seconds > MAX_SECONDS) { + return new Timestamp(0, 0); + } + if (nanos < 0 || nanos > MAX_NANOS) { + return new Timestamp(0, 0); + } + return new Timestamp(seconds, nanos); + } + + /** + * Creates a new timestamp from the given milliseconds. + * + * @return a new timestamp from the given milliseconds. + */ + public static Timestamp fromMillis(long millis) { + long seconds = millis / NUM_MILLIS_PER_SECOND; + int nanos = (int) (millis % NUM_MILLIS_PER_SECOND) * NUM_NANOS_PER_MILLI; + if (nanos < 0) { + return new Timestamp(seconds - 1, (int) (nanos + NUM_NANOS_PER_SECOND)); + } else { + return new Timestamp(seconds, nanos); + } + } + + /** + * Returns the number of seconds since the Unix Epoch represented by this timestamp. + * + * @return the number of seconds since the Unix Epoch. + */ + public long getSeconds() { + return seconds; + } + + /** + * 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. + */ + public int getNanos() { + return nanos; + } + + /** + * Returns a {@code Timestamp} calculated as this {@code Timestamp} plus some number of + * nanoseconds. + * + * @param nanos the nanoseconds to be added to the current timestamp. + * @return a {@code Timestamp} calculated as this {@code Timestamp} plus some number of + * nanoseconds. + */ + public Timestamp addNanos(long nanos) { + long newSeconds = seconds + nanos / NUM_NANOS_PER_SECOND; + nanos %= NUM_NANOS_PER_SECOND; + // Cannot overflow because: abs(nanos) < NUM_NANOS_PER_SECOND AND + // this.nanos < NUM_NANOS_PER_SECOND. + long newNanos = nanos + this.nanos; + newSeconds += (newNanos / NUM_NANOS_PER_SECOND); + newNanos %= NUM_NANOS_PER_SECOND; + if (newNanos >= 0) { + return Timestamp.create(newSeconds, (int) newNanos); + } else { + return Timestamp.create(newSeconds - 1, (int) (newNanos + NUM_NANOS_PER_SECOND)); + } + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof Timestamp)) { + return false; + } + + Timestamp that = (Timestamp) obj; + return seconds == that.seconds && nanos == that.nanos; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (int) (seconds ^ (seconds >>> 32)); + result = 31 * result + nanos; + return result; + } + + @Override + public String toString() { + return "Timestamp<" + seconds + "," + nanos + ">"; + } +} 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..148b6c38 --- /dev/null +++ b/api/src/main/java/io/opencensus/internal/Provider.java @@ -0,0 +1,99 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +/** + * Instrumentation specific service provider mechanism. + * + * <pre>{@code + * // Initialize a static variable using reflection. + * static final Foo foo = Provider.newInstance("Foo", new NoopFoo()); + * }</pre> + */ +public final class Provider { + private static final Logger logger = Logger.getLogger(Provider.class.getName()); + + /** + * Returns a new instance of the class specified with {@code name} by invoking the empty-argument + * constructor via reflections. If the specified class is not found, the {@code defaultValue} is + * returned. + */ + @SuppressWarnings("unchecked") + @Nullable + public static <T> T newInstance(String name, @Nullable T defaultValue) { + try { + Class<?> provider = Class.forName(name); + T result = (T) provider.getConstructor().newInstance(); + logger.fine("Loaded: " + name); + return result; + } catch (ClassNotFoundException e) { + logger.log(Level.FINE, "Falling back to " + defaultValue, e); + return defaultValue; + } catch (Exception e) { + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else { + throw new RuntimeException(e); + } + } + } + + /** + * 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); + } + } + + /** + * Get the correct {@link ClassLoader} that must be used when loading using reflection. + * + * @return The correct {@code ClassLoader} that must be used when loading using reflection. + */ + public static <T> ClassLoader getCorrectClassLoader(Class<T> superClass) { + if (isAndroid()) { + // When android:sharedUserId or android:process is used, Android will setup a dummy + // ClassLoader for the thread context (http://stackoverflow.com/questions/13407006), + // instead of letting users to manually set context class loader, we choose the + // correct class loader here. + return superClass.getClassLoader(); + } + return Thread.currentThread().getContextClassLoader(); + } + + private static boolean isAndroid() { + try { + Class.forName("android.app.Application", /*initialize=*/ false, null); + return true; + } catch (Exception e) { + // If Application isn't loaded, it might as well not be Android. + return false; + } + } +} diff --git a/api/src/main/java/io/opencensus/internal/StringUtil.java b/api/src/main/java/io/opencensus/internal/StringUtil.java new file mode 100644 index 00000000..48992ebd --- /dev/null +++ b/api/src/main/java/io/opencensus/internal/StringUtil.java @@ -0,0 +1,74 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 StringUtil { + + public static final int MAX_LENGTH = 255; + public static final char UNPRINTABLE_CHAR_SUBSTITUTE = '_'; + + /** + * Transforms the given {@code String} into a valid tag key, tag value, or metric name. This + * method replaces non-printable characters with underscores and truncates to {@link + * StringUtil#MAX_LENGTH}. + * + * @param str the {@code String} to be sanitized. + * @return the {@code String} with all non-printable characters replaced by underscores, truncated + * to {@code MAX_LENGTH}. + */ + public static String sanitize(String str) { + if (str.length() > MAX_LENGTH) { + str = str.substring(0, MAX_LENGTH); + } + if (isPrintableString(str)) { + return str; + } + StringBuilder builder = new StringBuilder(str.length()); + for (int i = 0; i < str.length(); i++) { + char ch = str.charAt(i); + builder.append(isPrintableChar(ch) ? ch : UNPRINTABLE_CHAR_SUBSTITUTE); + } + return builder.toString(); + } + + /** + * Determines whether the {@code String} is a valid tag key, tag value, or metric name. + * + * @param string the {@code String} to be validated. + * @return whether the {@code String} is valid. + * @see #sanitize(String) + */ + public static boolean isValid(String string) { + return string.length() <= MAX_LENGTH && isPrintableString(string); + } + + private 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 <= '~'; + } + + // Visible for testing + StringUtil() { + throw new AssertionError(); + } +} diff --git a/api/src/main/java/io/opencensus/internal/TestClock.java b/api/src/main/java/io/opencensus/internal/TestClock.java new file mode 100644 index 00000000..d181da3a --- /dev/null +++ b/api/src/main/java/io/opencensus/internal/TestClock.java @@ -0,0 +1,99 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 com.google.common.math.LongMath; +import io.opencensus.common.Clock; +import io.opencensus.common.Duration; +import io.opencensus.common.Timestamp; +import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.ThreadSafe; + +/** A {@link Clock} that allows the time to be set for testing. */ +@ThreadSafe +public final class TestClock extends Clock { + private static final int NUM_NANOS_PER_SECOND = 1000 * 1000 * 1000; + + @GuardedBy("this") + private Timestamp currentTime = validateNanos(Timestamp.create(1493419949, 223123456)); + + private TestClock() {} + + /** + * Creates a clock initialized to a constant non-zero time. {@code Timestamp.create(0, 0)} is not + * a good default, because it represents an invalid time. + * + * @return a clock initialized to a constant non-zero time. + */ + public static TestClock create() { + return new TestClock(); + } + + /** + * Creates a clock with the given time. + * + * @param time the initial time. + * @return a new {@code TestClock} with the given time. + */ + public static TestClock create(Timestamp time) { + TestClock clock = new TestClock(); + clock.setTime(time); + return clock; + } + + /** + * Sets the time. + * + * @param time the new time. + */ + public synchronized void setTime(Timestamp time) { + currentTime = validateNanos(time); + } + + /** + * Advances the time by a duration. + * + * @param duration the increase in time. + */ + // TODO(sebright): Consider adding an 'addDuration' method to Timestamp. + public synchronized void advanceTime(Duration duration) { + currentTime = + validateNanos( + Timestamp.create( + LongMath.checkedAdd(currentTime.getSeconds(), duration.getSeconds()), + currentTime.getNanos()) + .addNanos(duration.getNanos())); + } + + @Override + public synchronized Timestamp now() { + return currentTime; + } + + @Override + public synchronized long nowNanos() { + return getNanos(currentTime); + } + + private static Timestamp validateNanos(Timestamp time) { + getNanos(time); + return time; + } + + // Converts Timestamp into nanoseconds since time 0 and throws an exception if it overflows. + private static long getNanos(Timestamp time) { + return LongMath.checkedAdd( + LongMath.checkedMultiply(time.getSeconds(), NUM_NANOS_PER_SECOND), time.getNanos()); + } +} 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..022f02fe --- /dev/null +++ b/api/src/main/java/io/opencensus/internal/ZeroTimeClock.java @@ -0,0 +1,46 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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..5fe0010d --- /dev/null +++ b/api/src/main/java/io/opencensus/internal/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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/tags/TagKey.java b/api/src/main/java/io/opencensus/tags/TagKey.java new file mode 100644 index 00000000..970ee925 --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/TagKey.java @@ -0,0 +1,104 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags; + +import static com.google.common.base.Preconditions.checkArgument; +import static io.opencensus.tags.TagKey.TagType.TAG_BOOLEAN; +import static io.opencensus.tags.TagKey.TagType.TAG_LONG; +import static io.opencensus.tags.TagKey.TagType.TAG_STRING; + +import com.google.auto.value.AutoValue; +import io.opencensus.internal.StringUtil; +import javax.annotation.concurrent.Immutable; + +/** + * A key to a value stored in a {@link TagMap}. + * + * @param <TagValueT> The type of value that can be paired with this {@code TagKey}. {@code TagKey}s + * can only be instantiated with types {@code String}, {@code Long}, and {@code Boolean}. + */ +@Immutable +@AutoValue +public abstract class TagKey<TagValueT> { + /** The maximum length for a tag key name. */ + public static final int MAX_LENGTH = StringUtil.MAX_LENGTH; + + enum TagType { + TAG_STRING, + TAG_LONG, + TAG_BOOLEAN + } + + TagKey() {} + + /** + * Constructs a {@code TagKey<String>} 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. + * @throws IllegalArgumentException if the name is not valid. + */ + public static TagKey<String> createStringKey(String name) { + checkArgument(StringUtil.isValid(name)); + return new AutoValue_TagKey<String>(name, TAG_STRING); + } + + /** + * Constructs a {@code TagKey<Long>} 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. + * @throws IllegalArgumentException if the name is not valid. + */ + // TODO(sebright): Make this public once we support types other than String. + static TagKey<Long> createLongKey(String name) { + checkArgument(StringUtil.isValid(name)); + return new AutoValue_TagKey<Long>(name, TAG_LONG); + } + + /** + * Constructs a {@code TagKey<Boolean>} 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. + * @throws IllegalArgumentException if the name is not valid. + */ + // TODO(sebright): Make this public once we support types other than String. + static TagKey<Boolean> createBooleanKey(String name) { + checkArgument(StringUtil.isValid(name)); + return new AutoValue_TagKey<Boolean>(name, TAG_BOOLEAN); + } + + abstract String getName(); + + abstract TagType getTagType(); +} diff --git a/api/src/main/java/io/opencensus/tags/TagMap.java b/api/src/main/java/io/opencensus/tags/TagMap.java new file mode 100644 index 00000000..8edbda0d --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/TagMap.java @@ -0,0 +1,141 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags; + +import static com.google.common.base.Preconditions.checkArgument; + +import io.opencensus.internal.StringUtil; +import io.opencensus.tags.TagKey.TagType; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.concurrent.Immutable; + +/** + * A map from keys to values that can be used to label anything that is associated with a specific + * operation. + * + * <p>For example, {@code TagMap}s can be used to label stats, log messages, or debugging + * information. + */ +@Immutable +public final class TagMap { + /** The maximum length for a string tag value. */ + public static final int MAX_STRING_LENGTH = StringUtil.MAX_LENGTH; + + // The types of the TagKey and value must match for each entry. + private final Map<TagKey<?>, Object> tags; + + TagMap(Map<TagKey<?>, Object> tags) { + this.tags = Collections.unmodifiableMap(new HashMap<TagKey<?>, Object>(tags)); + } + + Map<TagKey<?>, Object> getTags() { + return tags; + } + + /** + * Returns a builder based on this {@code TagMap}. + * + * @return a builder based on this {@code TagMap}. + */ + public Builder toBuilder() { + return new Builder(getTags()); + } + + /** Builder for the {@link TagMap} class. */ + public static final class Builder { + private final Map<TagKey<?>, Object> tags; + + private Builder(Map<TagKey<?>, Object> tags) { + this.tags = new HashMap<TagKey<?>, Object>(tags); + } + + Builder() { + this.tags = new HashMap<TagKey<?>, Object>(); + } + + /** + * Adds the key/value pair regardless of whether the key is present. + * + * @param key the {@code TagKey} which will be set. + * @param value the value to set for the given key. + * @return this + * @throws IllegalArgumentException if either argument is null, the key is the wrong type, or + * the value contains unprintable characters or is longer than {@link + * TagMap#MAX_STRING_LENGTH}. + */ + public Builder set(TagKey<String> key, String value) { + checkArgument(key.getTagType() == TagType.TAG_STRING); + + // TODO(sebright): Consider adding a TagValue class to avoid validating the String every time + // it is set. + checkArgument(StringUtil.isValid(value)); + return setInternal(key, value); + } + + /** + * Adds the key/value pair regardless of whether the key is present. + * + * @param key the {@code TagKey} which will be set. + * @param value the value to set for the given key. + * @return this + * @throws IllegalArgumentException if the key is null or the key is the wrong type. + */ + // TODO(sebright): Make this public once we support types other than String. + Builder set(TagKey<Long> key, long value) { + checkArgument(key.getTagType() == TagType.TAG_LONG); + return setInternal(key, value); + } + + /** + * Adds the key/value pair regardless of whether the key is present. + * + * @param key the {@code TagKey} which will be set. + * @param value the value to set for the given key. + * @return this + * @throws IllegalArgumentException if the key is null or the key is the wrong type. + */ + // TODO(sebright): Make this public once we support types other than String. + Builder set(TagKey<Boolean> key, boolean value) { + checkArgument(key.getTagType() == TagType.TAG_BOOLEAN); + return setInternal(key, value); + } + + private <TagValueT> Builder setInternal(TagKey<TagValueT> key, TagValueT value) { + tags.put(key, value); + return this; + } + + /** + * Removes the key if it exists. + * + * @param key the {@code TagKey} which will be cleared. + * @return this + */ + public Builder clear(TagKey<?> key) { + tags.remove(key); + return this; + } + + /** + * Creates a {@code TagMap} from this builder. + * + * @return a {@code TagMap} with the same tags as this builder. + */ + public TagMap build() { + return new TagMap(new HashMap<TagKey<?>, Object>(tags)); + } + } +} 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..f72070e8 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/Annotation.java @@ -0,0 +1,73 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.auto.value.AutoValue; +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. */ +@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}. + */ + 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}. + */ + public static Annotation fromDescriptionAndAttributes( + String description, Map<String, AttributeValue> attributes) { + return new AutoValue_Annotation( + description, + Collections.unmodifiableMap( + new HashMap<String, AttributeValue>(checkNotNull(attributes, "attributes")))); + } + + /** + * Return the description of the {@code Annotation}. + * + * @return the description of the {@code Annotation}. + */ + public abstract String getDescription(); + + /** + * Return the attributes of the {@code Annotation}. + * + * @return the attributes of the {@code Annotation}. + */ + 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..f66fd425 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/AttributeValue.java @@ -0,0 +1,91 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.auto.value.AutoValue; +import javax.annotation.Nullable; +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}. + */ +@Immutable +@AutoValue +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}. + */ + public static AttributeValue stringAttributeValue(String stringValue) { + return new AutoValue_AttributeValue(checkNotNull(stringValue, "stringValue"), null, null); + } + + /** + * Returns an {@code AttributeValue} with a boolean value. + * + * @param booleanValue The new value. + * @return an {@code AttributeValue} with a boolean value. + */ + public static AttributeValue booleanAttributeValue(boolean booleanValue) { + return new AutoValue_AttributeValue(null, booleanValue, null); + } + + /** + * Returns an {@code AttributeValue} with a long value. + * + * @param longValue The new value. + * @return an {@code AttributeValue} with a long value. + */ + public static AttributeValue longAttributeValue(long longValue) { + return new AutoValue_AttributeValue(null, null, longValue); + } + + AttributeValue() {} + + /** + * Returns the {@code String} value if this is a string {@code AttributeValue}, otherwise {@code + * null}. + * + * @return the {@code String} value if this is a string {@code AttributeValue}, otherwise {@code + * null}. + */ + @Nullable + public abstract String getStringValue(); + + /** + * Returns the {@code Boolean} value if this is a boolean {@code AttributeValue}, otherwise {@code + * null}. + * + * @return the {@code Boolean} value if this is a boolean {@code AttributeValue}, otherwise {@code + * null}. + */ + @Nullable + public abstract Boolean getBooleanValue(); + + /** + * Returns the {@code Long} value if this is a long {@code AttributeValue}, otherwise {@code + * null}. + * + * @return the {@code Long} value if this is a long {@code AttributeValue}, otherwise {@code + * null}. + */ + @Nullable + public abstract Long getLongValue(); +} diff --git a/api/src/main/java/io/opencensus/trace/BinaryPropagationHandler.java b/api/src/main/java/io/opencensus/trace/BinaryPropagationHandler.java new file mode 100644 index 00000000..bc86dd7f --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/BinaryPropagationHandler.java @@ -0,0 +1,108 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.base.Preconditions.checkNotNull; + +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 BinaryPropagationHandler binaryPropagationHandler = + * Tracing.getBinaryPropagationHandler(); + * void onSendRequest() { + * try (NonThrowingCloseable ss = tracer.spanBuilder("Sent.MyRequest").startScopedSpan()) { + * byte[] binaryValue = binaryPropagationHandler.toBinaryValue( + * 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 BinaryPropagationHandler binaryPropagationHandler = + * Tracing.getBinaryPropagationHandler(); + * void onRequestReceived() { + * // Get the binaryValue from the request. + * SpanContext spanContext = SpanContext.INVALID; + * try { + * if (binaryValue != null) { + * spanContext = binaryPropagationHandler.fromBinaryValue(binaryValue); + * } + * } catch (ParseException e) { + * // Maybe log the exception. + * } + * try (NonThrowingCloseable ss = + * tracer.spanBuilderWithRemoteParent(spanContext, "Recv.MyRequest").startScopedSpan()) { + * // Handle request and send response back. + * } + * } + * }</pre> + */ +public abstract class BinaryPropagationHandler { + static final NoopBinaryPropagationHandler noopBinaryPropagationHandler = + new NoopBinaryPropagationHandler(); + + /** + * Serializes a {@link SpanContext} 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}. + */ + public abstract byte[] toBinaryValue(SpanContext spanContext); + + /** + * Parses the {@link SpanContext} from 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 ParseException if the version is not supported or the input is invalid + */ + public abstract SpanContext fromBinaryValue(byte[] bytes) throws ParseException; + + /** + * Returns the no-op implementation of the {@code BinaryPropagationHandler}. + * + * @return the no-op implementation of the {@code BinaryPropagationHandler}. + */ + static BinaryPropagationHandler getNoopBinaryPropagationHandler() { + return noopBinaryPropagationHandler; + } + + private static final class NoopBinaryPropagationHandler extends BinaryPropagationHandler { + @Override + public byte[] toBinaryValue(SpanContext spanContext) { + checkNotNull(spanContext, "spanContext"); + return new byte[0]; + } + + @Override + public SpanContext fromBinaryValue(byte[] bytes) throws ParseException { + checkNotNull(bytes, "bytes"); + return SpanContext.INVALID; + } + + private NoopBinaryPropagationHandler() {} + } +} 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..40e49551 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/BlankSpan.java @@ -0,0 +1,62 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.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}. + */ +@Immutable +public final class BlankSpan extends Span { + /** Singleton instance of this class. */ + public static final BlankSpan INSTANCE = new BlankSpan(); + + private BlankSpan() { + super(SpanContext.INVALID, null); + } + + /** No-op implementation of the {@link Span#addAttributes(Map)} method. */ + @Override + public void addAttributes(Map<String, AttributeValue> attributes) {} + + /** No-op implementation of the {@link Span#addAnnotation(String, Map)} method. */ + @Override + public void addAnnotation(String description, Map<String, AttributeValue> attributes) {} + + /** No-op implementation of the {@link Span#addAnnotation(Annotation)} method. */ + @Override + public void addAnnotation(Annotation annotation) {} + + /** No-op implementation of the {@link Span#addNetworkEvent(NetworkEvent)} method. */ + @Override + public void addNetworkEvent(NetworkEvent networkEvent) {} + + /** No-op implementation of the {@link Span#addLink(Link)} method. */ + @Override + public void addLink(Link link) {} + + /** No-op implementation of the {@link Span#end(EndSpanOptions)} method. */ + @Override + public void end(EndSpanOptions options) {} + + @Override + public String toString() { + return "BlankSpan"; + } +} diff --git a/api/src/main/java/io/opencensus/trace/ContextUtils.java b/api/src/main/java/io/opencensus/trace/ContextUtils.java new file mode 100644 index 00000000..c5331563 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/ContextUtils.java @@ -0,0 +1,75 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.NonThrowingCloseable; + +/** + * 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 Tracer} and avoid + * usages of the {@link #CONTEXT_SPAN_KEY} directly. + */ +public final class ContextUtils { + /** The {@link io.grpc.Context.Key} used to interact with {@link io.grpc.Context}. */ + public static final Context.Key<Span> CONTEXT_SPAN_KEY = Context.key("instrumentation-trace-key"); + + // No instance of this class. + private ContextUtils() {} + + /** + * Returns The {@link Span} from the current context. + * + * @return The {@code Span} from the current context. + */ + static Span getCurrentSpan() { + return CONTEXT_SPAN_KEY.get(Context.current()); + } + + /** + * 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. + * @return An object that defines a scope where the given {@code Span} is set to the current + * context. + */ + static NonThrowingCloseable withSpan(Span span) { + return new WithSpan(span, CONTEXT_SPAN_KEY); + } + + // Defines an arbitrary scope of code as a traceable operation. Supports try-with-resources idiom. + private static final class WithSpan implements NonThrowingCloseable { + private final Context origContext; + + /** + * Constructs a new {@link WithSpan}. + * + * @param span is the {@code Span} to be added to the current {@code io.grpc.Context}. + * @param contextKey is the {@code Context.Key} used to set/get {@code Span} from the {@code + * Context}. + */ + WithSpan(Span span, Context.Key<Span> contextKey) { + origContext = Context.current().withValue(contextKey, span).attach(); + } + + @Override + public void close() { + Context.current().detach(origContext); + } + } +} 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..e58a06c3 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/EndSpanOptions.java @@ -0,0 +1,78 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.auto.value.AutoValue; +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}. + */ +@Immutable +@AutoValue +public abstract class EndSpanOptions { + /** The default {@code EndSpanOptions}. */ + public static final EndSpanOptions DEFAULT = builder().build(); + + /** + * Returns a new {@link Builder} with default options. + * + * @return a new {@code Builder} with default options. + */ + public static Builder builder() { + return new AutoValue_EndSpanOptions.Builder().setStatus(Status.OK); + } + + /** + * Returns the status. + * + * @return the status. + */ + public abstract Status getStatus(); + + /** Builder class for {@link EndSpanOptions}. */ + @AutoValue.Builder + public abstract static class Builder { + /** + * Sets the status for the {@link Span}. + * + * <p>If set, this will override the default {@code Span} status. Default is {@link Status#OK}. + * + * @param status the status. + * @return this. + */ + public abstract Builder setStatus(Status status); + + abstract EndSpanOptions autoBuild(); // not public + + /** + * Builds and returns a {@code EndSpanOptions} with the desired settings. + * + * @return a {@code EndSpanOptions} with the desired settings. + * @throws NullPointerException if {@code status} is {@code null}. + */ + public EndSpanOptions build() { + EndSpanOptions endSpanOptions = autoBuild(); + checkNotNull(endSpanOptions.getStatus(), "status"); + return endSpanOptions; + } + + 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..d582b68b --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/Link.java @@ -0,0 +1,72 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 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. + */ +@Immutable +@AutoValue +public abstract class Link { + /** The relationship with the linked {@code Span} relative to the current {@code Span}. */ + public enum Type { + /** When the linked {@code Span} is a child of the current {@code Span}. */ + CHILD, + /** When the linked {@code Span} is a parent of the current {@code Span}. */ + PARENT + } + + /** + * 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}. + */ + public static Link fromSpanContext(SpanContext context, Type type) { + return new AutoValue_Link(context.getTraceId(), context.getSpanId(), type); + } + + /** + * Returns the {@code TraceId}. + * + * @return the {@code TraceId}. + */ + public abstract TraceId getTraceId(); + + /** + * Returns the {@code SpanId}. + * + * @return the {@code SpanId} + */ + public abstract SpanId getSpanId(); + + /** + * Returns the {@code Type}. + * + * @return the {@code Type}. + */ + public abstract Type getType(); + + Link() {} +} 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..10687074 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/NetworkEvent.java @@ -0,0 +1,124 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.Timestamp; +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. + */ +@Immutable +@AutoValue +public abstract class NetworkEvent { + /** Available types for a {@code NetworkEvent}. */ + public enum Type { + /** When the message was sent. */ + SENT, + /** When the message was received. */ + 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}. + */ + public static Builder builder(Type type, long messageId) { + return new AutoValue_NetworkEvent.Builder() + .setType(checkNotNull(type, "type")) + .setMessageId(messageId) + // We need to set a value for the message size because the autovalue requires all + // primitives to be initialized. + // TODO(bdrutu): Consider to change the API to require message size. + .setMessageSize(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. + */ + @Nullable + public abstract Timestamp getKernelTimestamp(); + + /** + * Returns the type of the {@code NetworkEvent}. + * + * @return the type of the {@code NetworkEvent}. + */ + 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}. + */ + public abstract long getMessageId(); + + /** + * Returns The message size in bytes of the {@code NetworkEvent}. + * + * @return The message size in bytes of the {@code NetworkEvent}. + */ + public abstract long getMessageSize(); + + /** Builder class for {@link NetworkEvent}. */ + @AutoValue.Builder + 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. + */ + public abstract Builder setKernelTimestamp(@Nullable Timestamp kernelTimestamp); + + /** + * Sets the message size. + * + * @param messageSize represents the size in bytes of this network message. + * @return this. + */ + public abstract Builder setMessageSize(long messageSize); + + /** + * Builds and returns a {@code NetworkEvent} with the desired values. + * + * @return a {@code NetworkEvent} with the desired values. + */ + 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..e5014fa4 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/Sampler.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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. */ +public abstract class Sampler { + /** + * Called during {@link Span} creation to make a sampling decision. + * + * @param parentContext The parent {@code Span} {@link SpanContext}. May be {@code null} if this + * is a root span. + * @param remoteParent true if the parentContext is remote. + * @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 span ID 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. + */ + protected abstract boolean shouldSample( + @Nullable SpanContext parentContext, + boolean remoteParent, + TraceId traceId, + SpanId spanId, + String name, + List<Span> parentLinks); +} diff --git a/api/src/main/java/io/opencensus/trace/Samplers.java b/api/src/main/java/io/opencensus/trace/Samplers.java new file mode 100644 index 00000000..33c2a006 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/Samplers.java @@ -0,0 +1,166 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.auto.value.AutoValue; +import java.util.List; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** Static class to access a set of pre-defined {@link Sampler Samplers}. */ +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. + */ + 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. + */ + 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 + */ + public static Sampler probabilitySampler(double probability) { + return ProbabilitySampler.create(probability); + } + + @Immutable + private static final class AlwaysSampleSampler extends Sampler { + private AlwaysSampleSampler() {} + + // Returns always makes a "yes" decision on {@link Span} sampling. + @Override + protected boolean shouldSample( + @Nullable SpanContext parentContext, + boolean remoteParent, + TraceId traceId, + SpanId spanId, + String name, + List<Span> parentLinks) { + return true; + } + + @Override + public String toString() { + return "AlwaysSampleSampler"; + } + } + + @Immutable + private static final class NeverSampleSampler extends Sampler { + private NeverSampleSampler() {} + + // Returns always makes a "no" decision on {@link Span} sampling. + @Override + protected boolean shouldSample( + @Nullable SpanContext parentContext, + boolean remoteParent, + TraceId traceId, + SpanId spanId, + String name, + List<Span> parentLinks) { + return false; + } + + @Override + public String toString() { + return "NeverSampleSampler"; + } + } + + // 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 static 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 + */ + private static ProbabilitySampler create(double probability) { + checkArgument( + probability >= 0.0 && probability <= 1.0, "probability must be in range [0.0, 1.0]"); + long idUpperBound = 0; + // 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_Samplers_ProbabilitySampler(probability, idUpperBound); + } + + @Override + protected final boolean shouldSample( + @Nullable SpanContext parentContext, + boolean remoteParent, + TraceId traceId, + SpanId spanId, + String name, + @Nullable List<Span> parentLinks) { + // Always enable sampling if parent was sampled. + if (parentContext != null && parentContext.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(); + } + } +} diff --git a/api/src/main/java/io/opencensus/trace/ScopedSpanHandle.java b/api/src/main/java/io/opencensus/trace/ScopedSpanHandle.java new file mode 100644 index 00000000..965c4d98 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/ScopedSpanHandle.java @@ -0,0 +1,47 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.NonThrowingCloseable; + +/** + * Defines a scope of code where the given {@link Span} is in the current context. The scope is + * exited when the object is closed and the previous context is restored. When the scope exits the + * given {@code Span} will be ended using {@link Span#end}. + * + * <p>Supports try-with-resource idiom. + */ +final class ScopedSpanHandle implements NonThrowingCloseable { + private final Span span; + private final NonThrowingCloseable withSpan; + + /** + * Creates a {@code ScopedSpanHandle} + * + * @param span The span that will be installed in the current context. + */ + ScopedSpanHandle(Span span) { + this.span = span; + this.withSpan = ContextUtils.withSpan(span); + } + + /** + * Uninstalls the {@code Span} from the current context and ends it by calling {@link Span#end()}. + */ + @Override + public void close() { + withSpan.close(); + span.end(); + } +} 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..76dcd9c7 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/Span.java @@ -0,0 +1,165 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +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)} + */ +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. + */ + public enum Options { + /** + * This option is set if the Span is part of a sampled distributed trace OR the {@link + * StartSpanOptions#getRecordEvents()} is true. + */ + 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. + */ + protected Span(SpanContext context, @Nullable EnumSet<Options> options) { + this.context = checkNotNull(context, "context"); + this.options = + options == null ? DEFAULT_OPTIONS : Collections.unmodifiableSet(EnumSet.copyOf(options)); + checkArgument( + !context.getTraceOptions().isSampled() || (this.options.contains(Options.RECORD_EVENTS)), + "Span is sampled, but does not have RECORD_EVENTS set."); + } + + /** + * Adds a set of attributes to the {@code Span}. + * + * @param attributes the attributes that will be added and associated with the {@code Span}. + */ + public abstract void addAttributes(Map<String, AttributeValue> attributes); + + /** + * Adds an annotation to the {@code Span}. + * + * @param description the description of the annotation time event. + */ + public final void addAnnotation(String 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 #addAttributes}. + */ + public abstract void addAnnotation(String description, Map<String, AttributeValue> attributes); + + /** + * Adds an annotation to the {@code Span}. + * + * @param annotation the annotations to add. + */ + 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. + */ + public abstract void addNetworkEvent(NetworkEvent networkEvent); + + /** + * 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. + */ + public abstract void addLink(Link link); + + /** + * 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}. + */ + 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. + */ + 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}. + */ + public final SpanContext getContext() { + return context; + } + + /** + * Returns the options associated with this {@code Span}. + * + * @return the options associated with this {@code Span}. + */ + public final Set<Options> getOptions() { + return options; + } +} 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..4b988dfc --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/SpanBuilder.java @@ -0,0 +1,282 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.NonThrowingCloseable; +import java.util.List; +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 (NonThrowingCloseable 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 (NonThrowingCloseable 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 span = tracer.spanBuilder(null, "MyRootSpan").startSpan(); + * span.addAnnotation("my annotation"); + * try { + * doSomeWork(span); // Manually propagate the new span down the stack. + * } finally { + * // To make sure we end the span even in case of an exception. + * span.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. + */ +public final class SpanBuilder { + private final SpanFactory spanFactory; + private final String name; + private final StartSpanOptions startSpanOption = new StartSpanOptions(); + private Span parentSpan; + private SpanContext parentSpanContext; + private boolean remoteParent; + + static SpanBuilder builder(SpanFactory spanFactory, Span parentSpan, String name) { + return new SpanBuilder(spanFactory, parentSpan, null, false, name); + } + + static SpanBuilder builderWithRemoteParent( + SpanFactory spanFactory, SpanContext parentSpanContext, String name) { + return new SpanBuilder(spanFactory, null, parentSpanContext, true, name); + } + + /** + * Sets the {@link Sampler} to use. If a {@code null} value is passed, the implementation will + * provide a default. + * + * @param sampler The {@code Sampler} to use when determining sampling for a {@code Span}. + * @return this. + */ + public SpanBuilder setSampler(@Nullable Sampler sampler) { + startSpanOption.setSampler(sampler); + return this; + } + + /** + * 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. + */ + public SpanBuilder setParentLinks(@Nullable List<Span> parentLinks) { + startSpanOption.setParentLinks(parentLinks); + return this; + } + + /** + * 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. + */ + public SpanBuilder setRecordEvents(boolean recordEvents) { + startSpanOption.setRecordEvents(recordEvents); + return this; + } + + /** + * If called this will force the newly created {@code Span} to be a root span. As a consequence, + * any parent specified (or inherited from the Context) will be ignored (N.B. does not apply to + * linked parents set through {@link #setParentLinks}). + * + * <p>This is useful when {@link Tracer#spanBuilder(String)} is used and the newly created {@code + * Span} needs to be decoupled from the parent {@code Span}. + * + * @return this. + */ + public SpanBuilder becomeRoot() { + parentSpan = null; + parentSpanContext = null; + remoteParent = false; + 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 span = tracer.spanBuilder(null, "MyRootSpan").startSpan(); + * span.addAnnotation("my annotation"); + * try { + * doSomeWork(span); // Manually propagate the new span down the stack. + * } finally { + * // To make sure we end the span even in case of an exception. + * span.end(); // Manually end the span. + * } + * } + * } + * }</pre> + * + * @return the newly created {@code Span}. + */ + public Span startSpan() { + return start(); + } + + // TODO(bdrutu): Add error_prone annotation @MustBeClosed when the 2.0.16 jar is fixed. + /** + * Starts a new 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. The scope is exited when the returned object is + * closed then 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 (NonThrowingCloseable 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. + * NonThrowingCloseable 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> + * + * @return an object that defines a scope where the newly created {@code Span} will be set to the + * current Context. + */ + public NonThrowingCloseable startScopedSpan() { + return new ScopedSpanHandle(start()); + } + + private SpanBuilder( + SpanFactory spanFactory, + @Nullable Span parentSpan, + @Nullable SpanContext parentSpanContext, + boolean remoteParent, + String name) { + this.parentSpan = parentSpan; + this.parentSpanContext = parentSpanContext; + this.remoteParent = remoteParent; + this.name = name; + this.spanFactory = spanFactory; + } + + // Utility method to start a Span. + private Span start() { + return remoteParent + ? spanFactory.startSpanWithRemoteParent(parentSpanContext, name, startSpanOption) + : spanFactory.startSpan(parentSpan, name, startSpanOption); + } +} 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..4902f15f --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/SpanContext.java @@ -0,0 +1,118 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.common.base.MoreObjects; +import com.google.common.base.Objects; +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}. + */ +@Immutable +public final class SpanContext { + private final TraceId traceId; + private final SpanId spanId; + private final TraceOptions traceOptions; + + /** The invalid {@code SpanContext}. */ + public static final SpanContext INVALID = + new SpanContext(TraceId.INVALID, SpanId.INVALID, TraceOptions.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. + */ + public static SpanContext create(TraceId traceId, SpanId spanId, TraceOptions traceOptions) { + return new SpanContext(traceId, spanId, traceOptions); + } + + /** + * Returns the trace identifier associated with this {@code SpanContext}. + * + * @return the trace identifier associated with this {@code SpanContext}. + */ + public TraceId getTraceId() { + return traceId; + } + + /** + * Returns the span identifier associated with this {@code SpanContext}. + * + * @return the span identifier associated with this {@code SpanContext}. + */ + public SpanId getSpanId() { + return spanId; + } + + /** + * Returns the trace options associated with this {@code SpanContext}. + * + * @return the trace options associated with this {@code SpanContext}. + */ + public TraceOptions getTraceOptions() { + return traceOptions; + } + + /** + * Returns true if this {@code SpanContext} is valid. + * + * @return true if this {@code SpanContext} is valid. + */ + public boolean isValid() { + return traceId.isValid() && spanId.isValid(); + } + + @Override + public boolean equals(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 Objects.hashCode(traceId, spanId, traceOptions); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("traceId", traceId) + .add("spanId", spanId) + .add("traceOptions", traceOptions) + .toString(); + } + + private SpanContext(TraceId traceId, SpanId spanId, TraceOptions traceOptions) { + this.traceId = traceId; + this.spanId = spanId; + this.traceOptions = traceOptions; + } +} diff --git a/api/src/main/java/io/opencensus/trace/SpanData.java b/api/src/main/java/io/opencensus/trace/SpanData.java new file mode 100644 index 00000000..f9a8140f --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/SpanData.java @@ -0,0 +1,308 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.Timestamp; +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; + +/** Immutable representation of all data collected by the {@link Span} class. */ +@Immutable +@AutoValue +public abstract class SpanData { + + /** + * 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 is on a different process. + * @param displayName the name 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 networkEvents the network events associated with the {@code Span}. + * @param links the links associated with the {@code Span}. + * @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}. + */ + public static SpanData create( + SpanContext context, + @Nullable SpanId parentSpanId, + boolean hasRemoteParent, + String displayName, + Timestamp startTimestamp, + Attributes attributes, + TimedEvents<Annotation> annotations, + TimedEvents<NetworkEvent> networkEvents, + Links links, + @Nullable Status status, + @Nullable Timestamp endTimestamp) { + return new AutoValue_SpanData( + context, + parentSpanId, + hasRemoteParent, + displayName, + startTimestamp, + attributes, + annotations, + networkEvents, + links, + status, + endTimestamp); + } + + /** + * Returns the {@code SpanContext} associated with this {@code Span}. + * + * @return the {@code SpanContext} associated with this {@code Span}. + */ + 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}. + */ + @Nullable + public abstract SpanId getParentSpanId(); + + /** + * Returns {@code true} if the parent is on a different process. + * + * @return {@code true} if the parent is on a different process. + */ + public abstract boolean getHasRemoteParent(); + + /** + * Returns the display name of this {@code Span}. + * + * @return the display name of this {@code Span}. + */ + public abstract String getDisplayName(); + + /** + * Returns the start {@code Timestamp} of this {@code Span}. + * + * @return the start {@code Timestamp} of this {@code Span}. + */ + public abstract Timestamp getStartTimestamp(); + + /** + * Returns the attributes recorded for this {@code Span}. + * + * @return the attributes recorded for this {@code Span}. + */ + public abstract Attributes getAttributes(); + + /** + * Returns the annotations recorded for this {@code Span}. + * + * @return the annotations recorded for this {@code Span}. + */ + public abstract TimedEvents<Annotation> getAnnotations(); + + /** + * Returns network events recorded for this {@code Span}. + * + * @return network events recorded for this {@code Span}. + */ + public abstract TimedEvents<NetworkEvent> getNetworkEvents(); + + /** + * Returns links recorded for this {@code Span}. + * + * @return links recorded for this {@code Span}. + */ + public abstract Links getLinks(); + + /** + * 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. + */ + @Nullable + 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. + */ + @Nullable + public abstract Timestamp getEndTimestamp(); + + SpanData() {} + + /** + * A timed event representation. + * + * @param <T> the type of value that is timed. + */ + @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>} + */ + 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. + */ + public abstract Timestamp getTimestamp(); + + /** + * Returns the event. + * + * @return the event. + */ + 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. + */ + @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>} + */ + public static <T> TimedEvents<T> create(List<TimedEvent<T>> events, int droppedEventsCount) { + return new AutoValue_SpanData_TimedEvents<T>( + Collections.unmodifiableList( + new ArrayList<TimedEvent<T>>(checkNotNull(events, "events"))), + droppedEventsCount); + } + + /** + * Returns the list of events. + * + * @return the list of events. + */ + public abstract List<TimedEvent<T>> getEvents(); + + /** + * Returns the number of dropped events. + * + * @return the number of dropped events. + */ + public abstract int getDroppedEventsCount(); + + TimedEvents() {} + } + + /** A set of attributes and the number of dropped attributes representation. */ + @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}. + */ + 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>(checkNotNull(attributeMap, "attributeMap"))), + droppedAttributesCount); + } + + /** + * Returns the set of attributes. + * + * @return the set of attributes. + */ + public abstract Map<String, AttributeValue> getAttributeMap(); + + /** + * Returns the number of dropped attributes. + * + * @return the number of dropped attributes. + */ + public abstract int getDroppedAttributesCount(); + + Attributes() {} + } + + /** A list of links and the number of dropped links representation. */ + @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}. + */ + public static Links create(List<Link> links, int droppedLinksCount) { + return new AutoValue_SpanData_Links( + Collections.unmodifiableList(new ArrayList<Link>(checkNotNull(links, "links"))), + droppedLinksCount); + } + + /** + * Returns the list of links. + * + * @return the list of links. + */ + public abstract List<Link> getLinks(); + + /** + * Returns the number of dropped links. + * + * @return the number of dropped links. + */ + public abstract int getDroppedLinksCount(); + + Links() {} + } +} diff --git a/api/src/main/java/io/opencensus/trace/SpanFactory.java b/api/src/main/java/io/opencensus/trace/SpanFactory.java new file mode 100644 index 00000000..aca5bd85 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/SpanFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 javax.annotation.Nullable; + +/** Factory class to create and start a {@link Span}. */ +public abstract class SpanFactory { + /** + * Creates and starts a new child {@link Span} (or root if parent is {@code null}), with parent + * being the designated {@code Span} and the given options. + * + * @param parent The parent of the returned {@code Span}. + * @param name The name of the returned {@code Span}. + * @param options The options for the start of the {@code Span}. + * @return A child {@code Span} that will have the name provided. + */ + protected abstract Span startSpan(@Nullable Span parent, String name, StartSpanOptions options); + + /** + * Creates and starts a new child {@link Span} (or root if parent is {@code null}), with parent + * being the {@code Span} designated by the {@link SpanContext} and the given options. + * + * <p>This must be used to create a {@code Span} when the parent is on a different process. + * + * @param remoteParent The remote parent of the returned {@code Span}. + * @param name The name of the returned {@code Span}. + * @param options The options for the start of the {@code Span}. + * @return A child {@code Span} that will have the name provided. + */ + protected abstract Span startSpanWithRemoteParent( + @Nullable SpanContext remoteParent, String name, StartSpanOptions options); +} 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..8128c9d7 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/SpanId.java @@ -0,0 +1,171 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.MoreObjects; +import com.google.common.io.BaseEncoding; +import java.util.Arrays; +import java.util.Random; +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. + */ +@Immutable +public final class SpanId implements Comparable<SpanId> { + /** The size in bytes of the {@code SpanId}. */ + public static final int SIZE = 8; + + /** The invalid {@code SpanId}. All bytes are 0. */ + 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}. + */ + public static SpanId fromBytes(byte[] buffer) { + checkNotNull(buffer, "buffer"); + 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}. + */ + public static SpanId fromBytes(byte[] src, int srcOffset) { + byte[] bytes = new byte[SIZE]; + System.arraycopy(src, srcOffset, bytes, 0, SIZE); + return new SpanId(bytes); + } + + /** + * Generates a new random {@code SpanId}. + * + * @param random The random number generator. + * @return a valid new {@code SpanId}. + */ + 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}. + */ + 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}. + */ + 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. + */ + public boolean isValid() { + return !Arrays.equals(bytes, INVALID.bytes); + } + + @Override + public boolean equals(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 MoreObjects.toStringHelper(this) + .add("spanId", BaseEncoding.base16().lowerCase().encode(bytes)) + .toString(); + } + + @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/StartSpanOptions.java b/api/src/main/java/io/opencensus/trace/StartSpanOptions.java new file mode 100644 index 00000000..01bc8885 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/StartSpanOptions.java @@ -0,0 +1,101 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.common.base.Objects; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +/** + * A class that enables overriding the default values used when starting a {@link Span}. Allows + * overriding the {@link Sampler sampler}, the parent links, and option to record all the events + * even if the {@code Span} is not sampled. + */ +public final class StartSpanOptions { + private Sampler sampler; + private List<Span> parentLinks; + private Boolean recordEvents; + + StartSpanOptions() { + this.sampler = null; + this.parentLinks = null; + this.recordEvents = null; + } + + /** + * Returns the {@link Sampler} to be used, or {@code null} if default. + * + * @return the {@code Sampler} to be used, or {@code null} if default. + */ + @Nullable + public Sampler getSampler() { + return sampler; + } + + /** + * Returns the parent links to be set for the {@link Span}. + * + * @return the parent links to be set for the {@code Span}. + */ + public List<Span> getParentLinks() { + // Return an unmodifiable list. + return parentLinks == null + ? Collections.<Span>emptyList() + : Collections.unmodifiableList(parentLinks); + } + + /** + * Returns the record events option setting. + * + * @return the record events option setting. + */ + @Nullable + public Boolean getRecordEvents() { + return recordEvents; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof StartSpanOptions)) { + return false; + } + + StartSpanOptions that = (StartSpanOptions) obj; + return Objects.equal(sampler, that.sampler) + && Objects.equal(parentLinks, that.parentLinks) + && Objects.equal(recordEvents, that.recordEvents); + } + + @Override + public int hashCode() { + return Objects.hashCode(sampler, parentLinks, recordEvents); + } + + void setSampler(@Nullable Sampler sampler) { + this.sampler = sampler; + } + + void setParentLinks(@Nullable List<Span> parentLinks) { + this.parentLinks = parentLinks; + } + + void setRecordEvents(@Nullable Boolean recordEvents) { + this.recordEvents = recordEvents; + } +} 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..22cb5287 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/Status.java @@ -0,0 +1,327 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.TreeMap; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * 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'");} + */ +@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. + */ + public enum CanonicalCode { + /** The operation completed successfully. */ + OK(0), + + /** The operation was cancelled (typically by the caller). */ + 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. + */ + 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). + */ + 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. + */ + DEADLINE_EXCEEDED(4), + + /** Some requested entity (e.g., file or directory) was not found. */ + NOT_FOUND(5), + + /** Some entity that we attempted to create (e.g., file or directory) already exists. */ + 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). + */ + PERMISSION_DENIED(7), + + /** + * Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system + * is out of space. + */ + 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. + */ + 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. + */ + 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. + */ + OUT_OF_RANGE(11), + + /** Operation is not implemented or not supported/enabled in this service. */ + 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. + */ + 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. + */ + UNAVAILABLE(14), + + /** Unrecoverable data loss or corruption. */ + DATA_LOSS(15), + + /** The request does not have valid authentication credentials for the operation. */ + 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. + */ + public int value() { + return value; + } + + 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. */ + public static final Status OK = CanonicalCode.OK.toStatus(); + /** The operation was cancelled (typically by the caller). */ + public static final Status CANCELLED = CanonicalCode.CANCELLED.toStatus(); + /** Unknown error. See {@link CanonicalCode#UNKNOWN}. */ + public static final Status UNKNOWN = CanonicalCode.UNKNOWN.toStatus(); + /** Client specified an invalid argument. See {@link CanonicalCode#INVALID_ARGUMENT}. */ + public static final Status INVALID_ARGUMENT = CanonicalCode.INVALID_ARGUMENT.toStatus(); + /** + * Deadline expired before operation could complete. See {@link CanonicalCode#DEADLINE_EXCEEDED}. + */ + public static final Status DEADLINE_EXCEEDED = CanonicalCode.DEADLINE_EXCEEDED.toStatus(); + /** Some requested entity (e.g., file or directory) was not found. */ + public static final Status NOT_FOUND = CanonicalCode.NOT_FOUND.toStatus(); + /** Some entity that we attempted to create (e.g., file or directory) already exists. */ + 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}. + */ + public static final Status PERMISSION_DENIED = CanonicalCode.PERMISSION_DENIED.toStatus(); + /** The request does not have valid authentication credentials for the operation. */ + 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. + */ + 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}. + */ + 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}. + */ + public static final Status ABORTED = CanonicalCode.ABORTED.toStatus(); + /** Operation was attempted past the valid range. See {@link CanonicalCode#OUT_OF_RANGE}. */ + public static final Status OUT_OF_RANGE = CanonicalCode.OUT_OF_RANGE.toStatus(); + /** Operation is not implemented or not supported/enabled in this service. */ + public static final Status UNIMPLEMENTED = CanonicalCode.UNIMPLEMENTED.toStatus(); + /** Internal errors. See {@link CanonicalCode#INTERNAL}. */ + public static final Status INTERNAL = CanonicalCode.INTERNAL.toStatus(); + /** The service is currently unavailable. See {@link CanonicalCode#UNAVAILABLE}. */ + public static final Status UNAVAILABLE = CanonicalCode.UNAVAILABLE.toStatus(); + /** Unrecoverable data loss or corruption. */ + public static final Status DATA_LOSS = CanonicalCode.DATA_LOSS.toStatus(); + + // The canonical code of this message. + private final CanonicalCode canonicalCode; + // An additional error message. + private final String description; + + private Status(CanonicalCode canonicalCode, @Nullable String description) { + this.canonicalCode = 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. + */ + public Status withDescription(String description) { + if (Objects.equal(this.description, description)) { + return this; + } + return new Status(this.canonicalCode, description); + } + + /** + * Returns the canonical status code. + * + * @return the canonical status code. + */ + public CanonicalCode getCanonicalCode() { + return canonicalCode; + } + + /** + * Returns the description of this {@code Status} for human consumption. + * + * @return the description of this {@code Status}. + */ + @Nullable + 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. + */ + 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(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof Status)) { + return false; + } + + Status that = (Status) obj; + return canonicalCode == that.canonicalCode && Objects.equal(description, that.description); + } + + /** + * Hash codes on Statuses are not well defined. + * + * @see #equals + */ + @Override + public int hashCode() { + return Objects.hashCode(canonicalCode, description); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("canonicalCode", canonicalCode) + .add("description", description) + .toString(); + } +} 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..1829417f --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/TraceComponent.java @@ -0,0 +1,107 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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; + +/** + * Class that holds the implementation instances for {@link Tracer}, {@link + * BinaryPropagationHandler}, {@link Clock}, {@link TraceExporter} and {@link TraceConfig}. + * + * <p>Unless otherwise noted all methods (on component) results are cacheable. + */ +public abstract class TraceComponent { + private static final NoopTraceComponent noopTraceComponent = new NoopTraceComponent(); + + /** + * 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. + */ + public abstract Tracer getTracer(); + + /** + * Returns the {@link BinaryPropagationHandler} with the provided implementations. If no + * implementation is provided then no-op implementation will be used. + * + * @return the {@code BinaryPropagationHandler} implementation. + */ + public abstract BinaryPropagationHandler getBinaryPropagationHandler(); + + /** + * Returns the {@link Clock} with the provided implementation. + * + * @return the {@code Clock} implementation. + */ + public abstract Clock getClock(); + + /** + * Returns the {@link TraceExporter} with the provided implementation. If no implementation is + * provided then no-op implementations will be used. + * + * @return the {@link TraceExporter} implementation. + */ + public abstract TraceExporter getTraceExporter(); + + /** + * 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. + */ + public abstract TraceConfig getTraceConfig(); + + // Disallow external overrides until we define the final API. + TraceComponent() {} + + /** + * 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 getNoopTraceComponent() { + return noopTraceComponent; + } + + private static final class NoopTraceComponent extends TraceComponent { + @Override + public Tracer getTracer() { + return Tracer.getNoopTracer(); + } + + @Override + public BinaryPropagationHandler getBinaryPropagationHandler() { + return BinaryPropagationHandler.getNoopBinaryPropagationHandler(); + } + + @Override + public Clock getClock() { + return ZeroTimeClock.getInstance(); + } + + @Override + public TraceExporter getTraceExporter() { + return TraceExporter.getNoopTraceExporter(); + } + + @Override + public TraceConfig getTraceConfig() { + return TraceConfig.getNoopTraceConfig(); + } + + private NoopTraceComponent() {} + } +} diff --git a/api/src/main/java/io/opencensus/trace/TraceConfig.java b/api/src/main/java/io/opencensus/trace/TraceConfig.java new file mode 100644 index 00000000..41fa75b8 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/TraceConfig.java @@ -0,0 +1,213 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.auto.value.AutoValue; +import javax.annotation.concurrent.Immutable; + +/** + * 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). + */ +public abstract class TraceConfig { + private static final NoopTraceConfig noopTraceConfig = new NoopTraceConfig(); + + /** + * Returns the active {@code TraceParams}. + * + * @return the active {@code TraceParams}. + */ + public abstract TraceParams getActiveTraceParams(); + + /** + * Updates the active {@link TraceParams}. + * + * @param traceParams the new active {@code TraceParams}. + */ + public abstract void updateActiveTraceParams(TraceParams traceParams); + + /** + * Temporary updates the active {@link TraceParams} for {@code durationNs} nanoseconds. + * + * @param traceParams the new active {@code TraceParams}. + * @param durationNs the duration for how long the new params will be active. + */ + public abstract void temporaryUpdateActiveTraceParams(TraceParams traceParams, long durationNs); + + /** + * Returns the no-op implementation of the {@code TraceConfig}. + * + * @return the no-op implementation of the {@code TraceConfig}. + */ + static TraceConfig getNoopTraceConfig() { + return noopTraceConfig; + } + + /** Class that holds global trace parameters. */ + @AutoValue + @Immutable + public abstract static 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_NETWORK_EVENTS = 128; + private static final int DEFAULT_SPAN_MAX_NUM_LINKS = 128; + + public static final TraceParams DEFAULT = + TraceParams.builder() + .setSampler(DEFAULT_SAMPLER) + .setMaxNumberOfAttributes(DEFAULT_SPAN_MAX_NUM_ATTRIBUTES) + .setMaxNumberOfAnnotations(DEFAULT_SPAN_MAX_NUM_ANNOTATIONS) + .setMaxNumberOfNetworkEvents(DEFAULT_SPAN_MAX_NUM_NETWORK_EVENTS) + .setMaxNumberOfLinks(DEFAULT_SPAN_MAX_NUM_LINKS) + .build(); + + /** + * Returns the global default {@code Sampler}. Used if no {@code Sampler} is provided in {@link + * StartSpanOptions}. + * + * @return the global default {@code Sampler}. + */ + 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}. + */ + 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}. + */ + public abstract int getMaxNumberOfAnnotations(); + + /** + * Returns the global default max number of {@link NetworkEvent} events per {@link Span}. + * + * @return the global default max number of {@code NetworkEvent} events per {@code Span}. + */ + public abstract int getMaxNumberOfNetworkEvents(); + + /** + * 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}. + */ + public abstract int getMaxNumberOfLinks(); + + private static Builder builder() { + return new AutoValue_TraceConfig_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. + */ + public abstract Builder toBuilder(); + + /** A {@code Builder} class for {@link TraceParams}. */ + @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. + */ + 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. + */ + 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. + */ + public abstract Builder setMaxNumberOfAnnotations(int maxNumberOfAnnotations); + + /** + * Sets the global default max number of {@link NetworkEvent} events per {@link Span}. + * + * @param maxNumberOfNetworkEvents the global default max number of {@link NetworkEvent} + * events per {@link Span}. It must be positive otherwise {@link #build()} will throw an + * exception. + * @return this. + */ + public abstract Builder setMaxNumberOfNetworkEvents(int 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. + */ + 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. + */ + public TraceParams build() { + TraceParams traceParams = autoBuild(); + checkNotNull(traceParams.getSampler(), "sampler"); + checkArgument(traceParams.getMaxNumberOfAttributes() > 0, "maxNumberOfAttributes"); + checkArgument(traceParams.getMaxNumberOfAnnotations() > 0, "maxNumberOfAnnotations"); + checkArgument(traceParams.getMaxNumberOfNetworkEvents() > 0, "maxNumberOfNetworkEvents"); + checkArgument(traceParams.getMaxNumberOfLinks() > 0, "maxNumberOfLinks"); + return traceParams; + } + } + } + + private static final class NoopTraceConfig extends TraceConfig { + + @Override + public TraceParams getActiveTraceParams() { + return TraceParams.DEFAULT; + } + + @Override + public void updateActiveTraceParams(TraceParams traceParams) {} + + @Override + public void temporaryUpdateActiveTraceParams(TraceParams traceParams, long durationNs) {} + } +} diff --git a/api/src/main/java/io/opencensus/trace/TraceExporter.java b/api/src/main/java/io/opencensus/trace/TraceExporter.java new file mode 100644 index 00000000..7da2d604 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/TraceExporter.java @@ -0,0 +1,609 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.auto.value.AutoValue; +import io.opencensus.trace.Status.CanonicalCode; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; + +/** + * The main exporting API for the trace library. + * + * <p>Implementation MUST ensure that all functions are thread safe. + */ +@ThreadSafe +public abstract class TraceExporter { + + private static final NoopTraceExporter noopTraceExporter = new NoopTraceExporter(); + + /** + * Returns the no-op implementation of the {@code TraceExporter}. + * + * @return the no-op implementation of the {@code TraceExporter}. + */ + static TraceExporter getNoopTraceExporter() { + return noopTraceExporter; + } + + /** + * Registers a new service handler that is used by the library to export {@code SpanData} for + * sampled spans (see {@link TraceOptions#isSampled()}). + * + * <p>Example of usage: + * + * <pre>{@code + * public static void main(String[] args) { + * Tracing.getTraceExporter().registerServiceHandler( + * "com.google.stackdriver.tracing", new StackdriverTracingServiceHandler()); + * // ... + * } + * }</pre> + * + * @param name the name of the service handler. Must be unique for each service. + * @param serviceHandler the service handler that is called for each ended sampled span. + */ + public abstract void registerServiceHandler(String name, ServiceHandler serviceHandler); + + /** + * Unregisters the service handler with the provided name. + * + * @param name the name of the service handler that will be unregistered. + */ + public abstract void unregisterServiceHandler(String name); + + /** + * Returns the {@code InProcessDebuggingHandler} that can be used to get useful debugging + * information such as (active spans, latency based sampled spans, error based sampled spans). + * + * @return the {@code InProcessDebuggingHandler} or {@code null} if in-process debugging is not + * supported. + */ + @Nullable + public abstract InProcessDebuggingHandler getInProcessDebuggingHandler(); + + /** + * This class allows users to access in-process debugging information such as (getting access to + * all active spans, support latency based sampled spans and error based sampled spans). + * + * <p>The active spans tracking is available for all the spans with the option {@link + * Span.Options#RECORD_EVENTS}. This functionality allows users to debug stuck operations or long + * living operations. + * + * <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)}). + */ + public abstract static class InProcessDebuggingHandler { + + InProcessDebuggingHandler() {} + + /** + * Returns the summary of all available in-process debugging data such as number of active + * spans, number of sampled spans in the latency based samples or error based samples. + * + * <p>Latency based sampled summary buckets and error based sampled summary buckets are + * available only for span names registered using {@link + * #registerSpanNamesForCollection(Collection)}. + * + * @return the summary of all available in-process debugging data. + */ + public abstract Summary getSummary(); + + /** + * Returns a list of active spans that match the {@code filter}. + * + * <p>Active spans are available for all the span names. + * + * @param filter used to filter the returned spans. + * @return a list of active spans that match the {@code filter}. + */ + public abstract Collection<SpanData> getActiveSpans(ActiveSpansFilter filter); + + /** + * 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}. + */ + public abstract Collection<SpanData> getLatencyBasedSampledSpans( + LatencyBasedSampledSpansFilter 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}. + */ + public abstract Collection<SpanData> getErrorBasedSampledSpans( + ErrorBasedSampledSpansFilter 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. + */ + 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. + */ + public abstract void unregisterSpanNamesForCollection(Collection<String> spanNames); + + /** The summary of all in-process debugging information. */ + @AutoValue + @Immutable + public abstract static class Summary { + + Summary() {} + + /** + * Returns a new instance of {@code Summary}. + * + * @param perSpanNameSummary a map with summary for each different span name. + * @return a new instance of {@code Summary}. + * @throws NullPointerException if {@code perSpanNameSummary} is {@code null}. + */ + public static Summary create(Map<String, PerSpanNameSummary> perSpanNameSummary) { + return new AutoValue_TraceExporter_InProcessDebuggingHandler_Summary( + Collections.unmodifiableMap( + new HashMap<String, PerSpanNameSummary>( + checkNotNull(perSpanNameSummary, "perSpanNameSummary")))); + } + + /** + * Returns a map with summary of available data for each different span name. + * + * @return a map with all the span names and the summary. + */ + public abstract Map<String, PerSpanNameSummary> getPerSpanNameSummary(); + + /** Summary of all available data for a span name. */ + @AutoValue + @Immutable + public abstract static class PerSpanNameSummary { + + PerSpanNameSummary() {} + + /** + * Returns a new instance of {@code PerSpanNameSummary}. + * + * @param numActiveSpans the number of sampled spans. + * @param latencyBucketSummaries the summary for the latency buckets. + * @param errorBucketSummaries the summary for the error buckets. + * @return a new instance of {@code PerSpanNameSummary}. + * @throws NullPointerException if {@code latencyBucketSummaries} or {@code + * errorBucketSummaries} are {@code null}. + * @throws IllegalArgumentException if {@code numActiveSpans} is negative. + */ + public static PerSpanNameSummary create( + int numActiveSpans, + List<LatencyBucketSummary> latencyBucketSummaries, + List<ErrorBucketSummary> errorBucketSummaries) { + checkArgument(numActiveSpans >= 0, "Negative numActiveSpans."); + return new AutoValue_TraceExporter_InProcessDebuggingHandler_Summary_PerSpanNameSummary( + numActiveSpans, + Collections.unmodifiableList( + new ArrayList<LatencyBucketSummary>( + checkNotNull(latencyBucketSummaries, "latencyBucketSummaries"))), + Collections.unmodifiableList( + new ArrayList<ErrorBucketSummary>( + checkNotNull(errorBucketSummaries, "errorBucketSummaries")))); + } + + /** + * Returns the number of active spans. + * + * @return the number of active spans. + */ + public abstract int getNumActiveSpans(); + + /** + * Returns the list of all latency based sampled buckets summary. + * + * <p>The list is sorted based on the lower latency boundary, and the upper bound of one + * match the lower bound of the next. Every bucket contains samples with latency within the + * interval [lowerBoundary, upperBoundary). + * + * @return the list of all latency based sampled buckets summary. + */ + public abstract List<LatencyBucketSummary> getLatencyBucketSummaries(); + + /** + * Returns the list of all error based sampled buckets summary. + * + * <p>The list is sorted based on the {@link CanonicalCode#value()} and contains an entry + * for each of the values other than {@link CanonicalCode#OK}. + * + * @return the list of all error based sampled buckets summary. + */ + public abstract List<ErrorBucketSummary> getErrorBucketSummaries(); + + /** + * Summary of a latency based sampled spans bucket. Contains {@code Span} samples with + * latency between [latencyLowerNs, latencyUpperNs). + */ + @AutoValue + @Immutable + public abstract static class LatencyBucketSummary { + + LatencyBucketSummary() {} + + /** + * Returns a new instance of {@code LatencyBucketSummary}. The latency of the samples is + * in the interval [latencyLowerNs, latencyUpperNs). + * + * @param numSamples the number of sampled spans. + * @param latencyLowerNs the latency lower bound. + * @param latencyUpperNs the latency upper bound. + * @return a new instance of {@code LatencyBucketSummary}. + * @throws IllegalArgumentException if {@code numSamples} or {@code latencyLowerNs} or + * {@code latencyUpperNs} are negative. + */ + public static LatencyBucketSummary create( + int numSamples, long latencyLowerNs, long latencyUpperNs) { + checkArgument(numSamples >= 0, "Negative numSamples."); + checkArgument(latencyLowerNs >= 0, "Negative latencyLowerNs"); + checkArgument(latencyUpperNs >= 0, "Negative latencyUpperNs"); + //CHECKSTYLE:OFF: Long class name. + return new AutoValue_TraceExporter_InProcessDebuggingHandler_Summary_PerSpanNameSummary_LatencyBucketSummary( + numSamples, latencyLowerNs, latencyUpperNs); + //CHECKSTYLE:ON: Long class name. + } + + /** + * Returns the number of sampled spans in this bucket. + * + * @return the number of sampled spans in this bucket. + */ + public abstract int getNumSamples(); + + /** + * Returns the latency lower bound of this bucket (inclusive). + * + * @return the latency lower bound of this bucket. + */ + public abstract long getLatencyLowerNs(); + + /** + * Returns the latency upper bound of this bucket (exclusive). + * + * @return the latency upper bound of this bucket. + */ + public abstract long getLatencyUpperNs(); + } + + /** Summary of an error based sampled spans bucket. */ + @AutoValue + @Immutable + public abstract static class ErrorBucketSummary { + + ErrorBucketSummary() {} + + /** + * Returns a new instance of {@code ErrorBucketSummary}. + * + * @param numSamples the number of sampled spans. + * @param canonicalCode the error code of the bucket. + * @return a new instance of {@code ErrorBucketSummary}. + * @throws NullPointerException if {@code canonicalCode} is {@code null}. + * @throws IllegalArgumentException if {@code canonicalCode} is {@link CanonicalCode#OK} + * or {@code numSamples} is negative. + */ + public static ErrorBucketSummary create(int numSamples, CanonicalCode canonicalCode) { + checkArgument(numSamples >= 0, "Negative numSamples."); + checkArgument(canonicalCode != CanonicalCode.OK, "Invalid canonical code."); + //CHECKSTYLE:OFF: Long class name. + return new AutoValue_TraceExporter_InProcessDebuggingHandler_Summary_PerSpanNameSummary_ErrorBucketSummary( + numSamples, canonicalCode); + //CHECKSTYLE:ON: Long class name. + } + + /** + * Returns the number of sampled spans in this bucket. + * + * @return the number of sampled spans in this bucket. + */ + public abstract int getNumSamples(); + + /** + * Returns the {@code CanonicalCode} for this bucket. Always different than {@link + * CanonicalCode#OK}. + * + * @return the {@code CanonicalCode} for this bucket. + */ + public abstract CanonicalCode getCanonicalCode(); + } + } + } + + /** + * Filter for active spans. Used to filter results returned by the {@link + * #getActiveSpans(ActiveSpansFilter)} request. + */ + @AutoValue + @Immutable + public abstract static class ActiveSpansFilter { + + ActiveSpansFilter() {} + + /** + * Returns a new instance of {@code ActiveSpansFilter}. + * + * <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 ActiveSpansFilter}. + * @throws NullPointerException if {@code spanName} is {@code null}. + * @throws IllegalArgumentException if {@code maxSpansToReturn} is negative. + */ + public static ActiveSpansFilter create(String spanName, int maxSpansToReturn) { + checkArgument(maxSpansToReturn >= 0, "Negative maxSpansToReturn."); + return new AutoValue_TraceExporter_InProcessDebuggingHandler_ActiveSpansFilter( + spanName, maxSpansToReturn); + } + + /** + * Returns the span name. + * + * @return the span name. + */ + 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. + */ + public abstract int getMaxSpansToReturn(); + } + + /** + * Filter for latency based sampled spans. Used to filter results returned by the {@link + * #getLatencyBasedSampledSpans(LatencyBasedSampledSpansFilter)} request. + */ + @AutoValue + @Immutable + public abstract static class LatencyBasedSampledSpansFilter { + + LatencyBasedSampledSpansFilter() {} + + /** + * Returns a new instance of {@code LatencyBasedSampledSpansFilter}. + * + * <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 LatencyBasedSampledSpansFilter}. + * @throws NullPointerException if {@code spanName} is {@code null}. + * @throws IllegalArgumentException if {@code maxSpansToReturn} or {@code latencyLowerNs} or + * {@code latencyUpperNs} are negative. + */ + public static LatencyBasedSampledSpansFilter create( + String spanName, long latencyLowerNs, long latencyUpperNs, int maxSpansToReturn) { + checkArgument(maxSpansToReturn >= 0, "Negative maxSpansToReturn."); + checkArgument(latencyLowerNs >= 0, "Negative latencyLowerNs"); + checkArgument(latencyUpperNs >= 0, "Negative latencyUpperNs"); + return new AutoValue_TraceExporter_InProcessDebuggingHandler_LatencyBasedSampledSpansFilter( + spanName, latencyLowerNs, latencyUpperNs, maxSpansToReturn); + } + + /** + * Returns the span name used by this filter. + * + * @return the span name used by this filter. + */ + public abstract String getSpanName(); + + /** + * Returns the latency lower bound of this bucket (inclusive). + * + * @return the latency lower bound of this bucket. + */ + public abstract long getLatencyLowerNs(); + + /** + * Returns the latency upper bound of this bucket (exclusive). + * + * @return the latency upper bound of this bucket. + */ + 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. + */ + public abstract int getMaxSpansToReturn(); + } + + /** Filter for error based sampled spans. */ + @AutoValue + @Immutable + public abstract static class ErrorBasedSampledSpansFilter { + + ErrorBasedSampledSpansFilter() {} + + /** + * Returns a new instance of {@code ErrorBasedSampledSpansFilter}. + * + * <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. + * @param maxSpansToReturn the maximum number of results to be returned. {@code 0} means all. + * @return a new instance of {@code ErrorBasedSampledSpansFilter}. + * @throws NullPointerException if {@code spanName} or {@code canonicalCode} are {@code null}. + * @throws IllegalArgumentException if {@code canonicalCode} is {@link CanonicalCode#OK} or + * {@code maxSpansToReturn} is negative. + */ + public static ErrorBasedSampledSpansFilter create( + String spanName, CanonicalCode canonicalCode, int maxSpansToReturn) { + checkArgument(canonicalCode != CanonicalCode.OK, "Invalid canonical code."); + checkArgument(maxSpansToReturn >= 0, "Negative maxSpansToReturn."); + return new AutoValue_TraceExporter_InProcessDebuggingHandler_ErrorBasedSampledSpansFilter( + spanName, canonicalCode, maxSpansToReturn); + } + + /** + * Returns the span name used by this filter. + * + * @return the span name used by this filter. + */ + public abstract String getSpanName(); + + /** + * Returns the canonical code used by this filter. Always different than {@link + * CanonicalCode#OK}. + * + * @return the canonical code used by this filter. + */ + 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. + */ + public abstract int getMaxSpansToReturn(); + } + } + + /** + * 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 TraceExporter using {@link + * #registerServiceHandler(String, ServiceHandler)}. + */ + public abstract static class ServiceHandler { + + /** + * 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. + */ + public abstract void export(Collection<SpanData> spanDataList); + } + + /** + * Implementation of the {@link ServiceHandler} which logs all the exported {@link SpanData}. + * + * <p>Example of usage: + * + * <pre>{@code + * public static void main(String[] args) { + * Tracing.getTraceExporter().registerServiceHandler( + * "io.opencensus.LoggingServiceHandler", LoggingServiceHandler.getInstance()); + * // ... + * } + * }</pre> + */ + @ThreadSafe + public static final class LoggingServiceHandler extends ServiceHandler { + + private static final Logger logger = Logger.getLogger(LoggingServiceHandler.class.getName()); + private static final String SERVICE_NAME = "io.opencensus.trace.LoggingServiceHandler"; + private static final LoggingServiceHandler INSTANCE = new LoggingServiceHandler(); + + private LoggingServiceHandler() {} + + /** + * Registers the {@code LoggingServiceHandler} to the {@code TraceExporter}. + * + * @param traceExporter the instance of the {@code TraceExporter} where this service is + * registered. + */ + public static void registerService(TraceExporter traceExporter) { + traceExporter.registerServiceHandler(SERVICE_NAME, INSTANCE); + } + + /** + * Unregisters the {@code LoggingServiceHandler} from the {@code TraceExporter}. + * + * @param traceExporter the instance of the {@code TraceExporter} from where this service is + * unregistered. + */ + public static void unregisterService(TraceExporter traceExporter) { + traceExporter.unregisterServiceHandler(SERVICE_NAME); + } + + @Override + public void export(Collection<SpanData> spanDataList) { + for (SpanData spanData : spanDataList) { + logger.log(Level.INFO, spanData.toString()); + } + } + } + + private static final class NoopTraceExporter extends TraceExporter { + + @Override + public void registerServiceHandler(String name, @Nullable ServiceHandler serviceHandler) {} + + @Override + public void unregisterServiceHandler(String name) {} + + @Nullable + @Override + public InProcessDebuggingHandler getInProcessDebuggingHandler() { + return null; + } + } +} 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..5684efdf --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/TraceId.java @@ -0,0 +1,185 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.MoreObjects; +import com.google.common.io.BaseEncoding; +import java.util.Arrays; +import java.util.Random; +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. + */ +@Immutable +public final class TraceId implements Comparable<TraceId> { + /** The size in bytes of the {@code TraceId}. */ + public static final int SIZE = 16; + + /** The invalid {@code TraceId}. All bytes are '\0'. */ + 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}. + */ + public static TraceId fromBytes(byte[] buffer) { + checkNotNull(buffer, "buffer"); + 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}. + */ + public static TraceId fromBytes(byte[] src, int srcOffset) { + byte[] bytes = new byte[SIZE]; + System.arraycopy(src, srcOffset, bytes, 0, SIZE); + return new TraceId(bytes); + } + + /** + * Generates a new random {@code TraceId}. + * + * @param random the random number generator. + * @return a new valid {@code TraceId}. + */ + 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}. + */ + 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}. + */ + 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. + */ + public boolean isValid() { + return !Arrays.equals(bytes, INVALID.bytes); + } + + // Return the lower 8 bytes of the trace-id as a long value, assuming little-endian order. This + // is used in ProbabilitySampler. + 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(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 MoreObjects.toStringHelper(this) + .add("traceId", BaseEncoding.base16().lowerCase().encode(bytes)) + .toString(); + } + + @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..6fc04bb9 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/TraceOptions.java @@ -0,0 +1,210 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import javax.annotation.concurrent.Immutable; + +/** + * A class that represents global trace options. These options are propagated to all child {@link + * Span spans}. These determine features such as whether a {@code Span} should be traced. It is + * implemented as a bitmask. + */ +@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}. */ + public static final int SIZE = 1; + + /** The default {@code TraceOptions}. */ + public static final TraceOptions DEFAULT = new TraceOptions(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}. + */ + public static TraceOptions fromBytes(byte[] buffer) { + checkNotNull(buffer, "buffer"); + checkArgument(buffer.length == SIZE, "Invalid size: expected %s, got %s", SIZE, buffer.length); + return new TraceOptions(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}. + */ + public static TraceOptions fromBytes(byte[] src, int srcOffset) { + checkElementIndex(srcOffset, src.length); + return new TraceOptions(src[srcOffset]); + } + + /** + * Returns the 1-byte array representation of the {@code TraceOptions}. + * + * @return the 1-byte array representation of the {@code TraceOptions}. + */ + 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}. + */ + public void copyBytesTo(byte[] dest, int destOffset) { + checkElementIndex(destOffset, dest.length); + dest[destOffset] = options; + } + + /** + * Returns a new {@link Builder} with default options. + * + * @return a new {@code Builder} with default options. + */ + 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. + */ + 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. + */ + public boolean isSampled() { + return hasOption(IS_SAMPLED); + } + + @Override + public boolean equals(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 Objects.hashCode(options); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("sampled", isSampled()).toString(); + } + + /** Builder class for {@link TraceOptions}. */ + public static final class Builder { + private byte options; + + private Builder(byte options) { + this.options = options; + } + + /** + * Marks this trace as sampled. + * + * @return this. + */ + public Builder setIsSampled() { + options |= IS_SAMPLED; + return this; + } + + /** + * Builds and returns a {@code TraceOptions} with the desired options. + * + * @return a {@code TraceOptions} with the desired options. + */ + public TraceOptions build() { + return new TraceOptions(options); + } + } + + // Returns the current set of options bitmask. + @VisibleForTesting + 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..56c4280d --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/Tracer.java @@ -0,0 +1,228 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.base.Preconditions.checkNotNull; + +import io.opencensus.common.NonThrowingCloseable; +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(NonThrowingCloseable 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 span = tracer.spanBuilder(null, "MyRootSpan").startSpan(); + * span.addAnnotation("Starting the work."); + * try { + * doSomeWork(span); // Manually propagate the new span down the stack. + * } finally { + * span.addAnnotation("Finished working."); + * // To make sure we end the span even in case of an exception. + * span.end(); // Manually end the span. + * } + * } + * } + * }</pre> + */ +public abstract class Tracer { + private static final NoopTracer noopTracer = new NoopTracer(); + private final SpanFactory spanFactory; + + /** + * 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. + */ + public final Span getCurrentSpan() { + Span currentSpan = ContextUtils.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 (NonThrowingCloseable 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(); + * NonThrowingCloseable 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 span is null. + */ + public final NonThrowingCloseable withSpan(Span span) { + return ContextUtils.withSpan(checkNotNull(span, "span")); + } + + /** + * 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 create a root Span with the default options. + * + * <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. + * + * @param name The name of the returned Span. + * @return a {@code SpanBuilder} to create and start a new {@code Span}. + * @throws NullPointerException if name is null. + */ + public final SpanBuilder spanBuilder(String name) { + return spanBuilder(ContextUtils.getCurrentSpan(), name); + } + + /** + * Returns a {@link SpanBuilder} to create and start a new child {@link Span} (or root if parent + * is null), 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. + * + * @param parent The parent of the returned Span. If null the {@code SpanBuilder} will build a + * root {@code Span}. + * @param name The name of the returned Span. + * @return a {@code SpanBuilder} to create and start a new {@code Span}. + * @throws NullPointerException if name is null. + */ + public final SpanBuilder spanBuilder(@Nullable Span parent, String name) { + return SpanBuilder.builder(spanFactory, parent, checkNotNull(name, "name")); + } + + /** + * Returns a {@link SpanBuilder} to create and start a new child {@link Span} (or root if parent + * is null), with parent being the {@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. + * + * @param remoteParent The remote parent of the returned Span. + * @param name The name of the returned Span. + * @return a {@code SpanBuilder} to create and start a new {@code Span}. + * @throws NullPointerException if name is null. + */ + public final SpanBuilder spanBuilderWithRemoteParent( + @Nullable SpanContext remoteParent, String name) { + return SpanBuilder.builderWithRemoteParent( + spanFactory, remoteParent, checkNotNull(name, "name")); + } + + // No-Op implementation of the Tracer. + private static final class NoopTracer extends Tracer { + private NoopTracer() { + super(new NoopSpanFactory()); + } + + // No-op implementation of the SpanFactory + private static final class NoopSpanFactory extends SpanFactory { + @Override + protected Span startSpan(@Nullable Span parent, String name, StartSpanOptions options) { + return BlankSpan.INSTANCE; + } + + @Override + protected Span startSpanWithRemoteParent( + @Nullable SpanContext remoteParent, String name, StartSpanOptions options) { + return BlankSpan.INSTANCE; + } + } + } + + protected Tracer(SpanFactory spanFactory) { + this.spanFactory = checkNotNull(spanFactory, "spanFactory"); + } +} 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..e6d34a45 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/Tracing.java @@ -0,0 +1,89 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.common.annotations.VisibleForTesting; +import io.opencensus.common.Clock; +import io.opencensus.internal.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** Class that manages a global instance of the {@link TraceComponent}. */ +public final class Tracing { + private static final Logger logger = Logger.getLogger(Tracer.class.getName()); + private static final TraceComponent traceComponent = + loadTraceComponent(Provider.getCorrectClassLoader(TraceComponent.class)); + + /** + * Returns the global {@link Tracer}. + * + * @return the global {@code Tracer}. + */ + public static Tracer getTracer() { + return traceComponent.getTracer(); + } + + /** + * Returns the global {@link BinaryPropagationHandler}. + * + * @return the global {@code BinaryPropagationHandler}. + */ + public static BinaryPropagationHandler getBinaryPropagationHandler() { + return traceComponent.getBinaryPropagationHandler(); + } + + /** + * Returns the global {@link Clock}. + * + * @return the global {@code Clock}. + */ + public static Clock getClock() { + return traceComponent.getClock(); + } + + /** + * Returns the global {@link TraceExporter}. + * + * @return the global {@code TraceExporter}. + */ + public static TraceExporter getTraceExporter() { + return traceComponent.getTraceExporter(); + } + + /** + * Returns the global {@link TraceConfig}. + * + * @return the global {@code TraceConfig}. + */ + public static TraceConfig getTraceConfig() { + return traceComponent.getTraceConfig(); + } + + // Any provider that may be used for TraceComponent can be added here. + @VisibleForTesting + static TraceComponent loadTraceComponent(ClassLoader classLoader) { + try { + // Call Class.forName with literal string name of the class to help shading tools. + return Provider.createInstance( + Class.forName("io.opencensus.trace.TraceComponentImpl", true, classLoader), + TraceComponent.class); + } catch (ClassNotFoundException e) { + logger.log(Level.FINE, "Using default implementation for TraceComponent.", e); + } + return TraceComponent.getNoopTraceComponent(); + } + + // No instance of this class. + private Tracing() {} +} diff --git a/api/src/test/java/io/opencensus/common/DurationTest.java b/api/src/test/java/io/opencensus/common/DurationTest.java new file mode 100644 index 00000000..b9861eea --- /dev/null +++ b/api/src/test/java/io/opencensus/common/DurationTest.java @@ -0,0 +1,61 @@ +package io.opencensus.common; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link Duration}. */ +@RunWith(JUnit4.class) +public class DurationTest { + @Test + public void testDurationCreate() { + assertThat(Duration.create(24, 42).getSeconds()).isEqualTo(24); + assertThat(Duration.create(24, 42).getNanos()).isEqualTo(42); + assertThat(Duration.create(-24, -42).getSeconds()).isEqualTo(-24); + assertThat(Duration.create(-24, -42).getNanos()).isEqualTo(-42); + assertThat(Duration.create(315576000000L, 999999999).getSeconds()).isEqualTo(315576000000L); + assertThat(Duration.create(315576000000L, 999999999).getNanos()).isEqualTo(999999999); + assertThat(Duration.create(-315576000000L, -999999999).getSeconds()).isEqualTo(-315576000000L); + assertThat(Duration.create(-315576000000L, -999999999).getNanos()).isEqualTo(-999999999); + } + + @Test + public void testDurationCreateInvalidInput() { + assertThat(Duration.create(-315576000001L, 0)).isEqualTo(Duration.create(0, 0)); + assertThat(Duration.create(315576000001L, 0)).isEqualTo(Duration.create(0, 0)); + assertThat(Duration.create(0, 1000000000)).isEqualTo(Duration.create(0, 0)); + assertThat(Duration.create(0, -1000000000)).isEqualTo(Duration.create(0, 0)); + assertThat(Duration.create(-1, 1)).isEqualTo(Duration.create(0, 0)); + assertThat(Duration.create(1, -1)).isEqualTo(Duration.create(0, 0)); + } + + @Test + public void testDurationFromMillis() { + assertThat(Duration.fromMillis(0)).isEqualTo(Duration.create(0, 0)); + assertThat(Duration.fromMillis(987)).isEqualTo(Duration.create(0, 987000000)); + assertThat(Duration.fromMillis(3456)).isEqualTo(Duration.create(3, 456000000)); + } + + @Test + public void testDurationFromMillisNegative() { + assertThat(Duration.fromMillis(-1)).isEqualTo(Duration.create(0, -1000000)); + assertThat(Duration.fromMillis(-999)).isEqualTo(Duration.create(0, -999000000)); + assertThat(Duration.fromMillis(-1000)).isEqualTo(Duration.create(-1, 0)); + assertThat(Duration.fromMillis(-3456)).isEqualTo(Duration.create(-3, -456000000)); + } + + @Test + public void testDurationEqual() { + // Positive tests. + assertThat(Duration.create(0, 0)).isEqualTo(Duration.create(0, 0)); + assertThat(Duration.create(24, 42)).isEqualTo(Duration.create(24, 42)); + assertThat(Duration.create(-24, -42)).isEqualTo(Duration.create(-24, -42)); + // Negative tests. + assertThat(Duration.create(25, 42)).isNotEqualTo(Duration.create(24, 42)); + assertThat(Duration.create(24, 43)).isNotEqualTo(Duration.create(24, 42)); + assertThat(Duration.create(-25, -42)).isNotEqualTo(Duration.create(-24, -42)); + assertThat(Duration.create(-24, -43)).isNotEqualTo(Duration.create(-24, -42)); + } +} diff --git a/api/src/test/java/io/opencensus/common/TimestampTest.java b/api/src/test/java/io/opencensus/common/TimestampTest.java new file mode 100644 index 00000000..5a02264b --- /dev/null +++ b/api/src/test/java/io/opencensus/common/TimestampTest.java @@ -0,0 +1,83 @@ +package io.opencensus.common; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link Timestamp}. */ +@RunWith(JUnit4.class) +public class TimestampTest { + @Test + public void timestampCreate() { + assertThat(Timestamp.create(24, 42).getSeconds()).isEqualTo(24); + assertThat(Timestamp.create(24, 42).getNanos()).isEqualTo(42); + assertThat(Timestamp.create(-24, 42).getSeconds()).isEqualTo(-24); + assertThat(Timestamp.create(-24, 42).getNanos()).isEqualTo(42); + assertThat(Timestamp.create(315576000000L, 999999999).getSeconds()).isEqualTo(315576000000L); + assertThat(Timestamp.create(315576000000L, 999999999).getNanos()).isEqualTo(999999999); + assertThat(Timestamp.create(-315576000000L, 999999999).getSeconds()).isEqualTo(-315576000000L); + assertThat(Timestamp.create(-315576000000L, 999999999).getNanos()).isEqualTo(999999999); + } + + @Test + public void timestampCreate_InvalidInput() { + assertThat(Timestamp.create(-315576000001L, 0)).isEqualTo(Timestamp.create(0, 0)); + assertThat(Timestamp.create(315576000001L, 0)).isEqualTo(Timestamp.create(0, 0)); + assertThat(Timestamp.create(1, 1000000000)).isEqualTo(Timestamp.create(0, 0)); + assertThat(Timestamp.create(1, -1)).isEqualTo(Timestamp.create(0, 0)); + assertThat(Timestamp.create(-1, 1000000000)).isEqualTo(Timestamp.create(0, 0)); + assertThat(Timestamp.create(-1, -1)).isEqualTo(Timestamp.create(0, 0)); + } + + @Test + public void timestampFromMillis() { + assertThat(Timestamp.fromMillis(0)).isEqualTo(Timestamp.create(0, 0)); + assertThat(Timestamp.fromMillis(987)).isEqualTo(Timestamp.create(0, 987000000)); + assertThat(Timestamp.fromMillis(3456)).isEqualTo(Timestamp.create(3, 456000000)); + } + + @Test + public void timestampFromMillis_Negative() { + assertThat(Timestamp.fromMillis(-1)).isEqualTo(Timestamp.create(-1, 999000000)); + assertThat(Timestamp.fromMillis(-999)).isEqualTo(Timestamp.create(-1, 1000000)); + assertThat(Timestamp.fromMillis(-3456)).isEqualTo(Timestamp.create(-4, 544000000)); + } + + @Test + public void timestampAddNanos() { + Timestamp timestamp = Timestamp.create(1234, 223); + assertThat(timestamp.addNanos(0)).isEqualTo(timestamp); + assertThat(timestamp.addNanos(999999777)).isEqualTo(Timestamp.create(1235, 0)); + assertThat(timestamp.addNanos(1300200500)).isEqualTo(Timestamp.create(1235, 300200723)); + assertThat(timestamp.addNanos(1999999777)).isEqualTo(Timestamp.create(1236, 0)); + assertThat(timestamp.addNanos(9876543789L)).isEqualTo(Timestamp.create(1243, 876544012)); + assertThat(timestamp.addNanos(Long.MAX_VALUE)) + .isEqualTo(Timestamp.create(1234L + 9223372036L, 223 + 854775807)); + } + + @Test + public void timestampAddNanos_Negative() { + Timestamp timestamp = Timestamp.create(1234, 223); + assertThat(timestamp.addNanos(-223)).isEqualTo(Timestamp.create(1234, 0)); + assertThat(timestamp.addNanos(-1000000223)).isEqualTo(Timestamp.create(1233, 0)); + assertThat(timestamp.addNanos(-1300200500)).isEqualTo(Timestamp.create(1232, 699799723)); + assertThat(timestamp.addNanos(-4123456213L)).isEqualTo(Timestamp.create(1229, 876544010)); + assertThat(timestamp.addNanos(Long.MIN_VALUE)) + .isEqualTo(Timestamp.create(1234L - 9223372036L - 1, 223 + 145224192)); + } + + @Test + public void testTimestampEqual() { + // Positive tests. + assertThat(Timestamp.create(0, 0)).isEqualTo(Timestamp.create(0, 0)); + assertThat(Timestamp.create(24, 42)).isEqualTo(Timestamp.create(24, 42)); + assertThat(Timestamp.create(-24, 42)).isEqualTo(Timestamp.create(-24, 42)); + // Negative tests. + assertThat(Timestamp.create(25, 42)).isNotEqualTo(Timestamp.create(24, 42)); + assertThat(Timestamp.create(24, 43)).isNotEqualTo(Timestamp.create(24, 42)); + assertThat(Timestamp.create(-25, 42)).isNotEqualTo(Timestamp.create(-24, 42)); + assertThat(Timestamp.create(-24, 43)).isNotEqualTo(Timestamp.create(-24, 42)); + } +} diff --git a/api/src/test/java/io/opencensus/internal/ProviderTest.java b/api/src/test/java/io/opencensus/internal/ProviderTest.java new file mode 100644 index 00000000..f6aba5ec --- /dev/null +++ b/api/src/test/java/io/opencensus/internal/ProviderTest.java @@ -0,0 +1,125 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.internal; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.ServiceConfigurationError; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link Provider} */ +@RunWith(JUnit4.class) +public class ProviderTest { + static class GoodClass { + public GoodClass() {} + } + + static class PrivateConstructorClass { + private PrivateConstructorClass() {} + } + + static class NoDefaultConstructorClass { + public NoDefaultConstructorClass(int arg) {} + } + + private static class PrivateClass {} + + static interface MyInterface {} + + static class MyInterfaceImpl implements MyInterface { + public MyInterfaceImpl() {} + } + + @Test + public void testGoodClass() { + assertThat( + Provider.<GoodClass>newInstance("io.opencensus.internal.ProviderTest$GoodClass", null)) + .isNotNull(); + } + + @Test + public void testBadClass() { + assertThat( + Provider.<GoodClass>newInstance("io.opencensus.internal.ProviderTest$BadClass", null)) + .isNull(); + } + + @Test(expected = ServiceConfigurationError.class) + public void createInstance_ThrowsErrorWhenClassIsPrivate() throws ClassNotFoundException { + Provider.createInstance( + Class.forName( + "io.opencensus.internal.ProviderTest$PrivateClass", + true, + Provider.getCorrectClassLoader(ProviderTest.class)), + PrivateClass.class); + } + + @Test(expected = ServiceConfigurationError.class) + public void createInstance_ThrowsErrorWhenClassHasPrivateConstructor() + throws ClassNotFoundException { + Provider.createInstance( + Class.forName( + "io.opencensus.internal.ProviderTest$PrivateConstructorClass", + true, + Provider.getCorrectClassLoader(ProviderTest.class)), + PrivateConstructorClass.class); + } + + @Test(expected = ServiceConfigurationError.class) + public void createInstance_ThrowsErrorWhenClassDoesNotHaveDefaultConstructor() + throws ClassNotFoundException { + Provider.createInstance( + Class.forName( + "io.opencensus.internal.ProviderTest$NoDefaultConstructorClass", + true, + Provider.getCorrectClassLoader(ProviderTest.class)), + NoDefaultConstructorClass.class); + } + + @Test(expected = ServiceConfigurationError.class) + public void createInstance_ThrowsErrorWhenClassIsNotASubclass() throws ClassNotFoundException { + Provider.createInstance( + Class.forName( + "io.opencensus.internal.ProviderTest$GoodClass", + true, + Provider.getCorrectClassLoader(ProviderTest.class)), + MyInterface.class); + } + + @Test + public void createInstance_GoodClass() throws ClassNotFoundException { + assertThat( + Provider.createInstance( + Class.forName( + "io.opencensus.internal.ProviderTest$GoodClass", + true, + Provider.getCorrectClassLoader(ProviderTest.class)), + GoodClass.class)) + .isNotNull(); + } + + @Test + public void createInstance_GoodSubclass() throws ClassNotFoundException { + assertThat( + Provider.createInstance( + Class.forName( + "io.opencensus.internal.ProviderTest$MyInterfaceImpl", + true, + Provider.getCorrectClassLoader(ProviderTest.class)), + MyInterface.class)) + .isNotNull(); + } +} diff --git a/api/src/test/java/io/opencensus/internal/StringUtilTest.java b/api/src/test/java/io/opencensus/internal/StringUtilTest.java new file mode 100644 index 00000000..14144391 --- /dev/null +++ b/api/src/test/java/io/opencensus/internal/StringUtilTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.internal; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.Arrays; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link StringUtil}. */ +@RunWith(JUnit4.class) +public final class StringUtilTest { + @Test + public void testMaxLength() { + char[] string = new char[StringUtil.MAX_LENGTH]; + char[] truncString = new char[StringUtil.MAX_LENGTH + 10]; + Arrays.fill(string, 'v'); + Arrays.fill(truncString, 'v'); + assertThat(StringUtil.sanitize(new String(truncString))).isEqualTo(new String(string)); + } + + @Test + public void testBadChar() { + String string = "\2ab\3cd"; + assertThat(StringUtil.sanitize(string)).isEqualTo("_ab_cd"); + } + + @Test(expected = AssertionError.class) + public void testConstructor() { + new StringUtil(); + } +} diff --git a/api/src/test/java/io/opencensus/internal/TestClockTest.java b/api/src/test/java/io/opencensus/internal/TestClockTest.java new file mode 100644 index 00000000..fe3f80cb --- /dev/null +++ b/api/src/test/java/io/opencensus/internal/TestClockTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.internal; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.common.Duration; +import io.opencensus.common.Timestamp; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link TestClock}. */ +@RunWith(JUnit4.class) +public final class TestClockTest { + private static final int NUM_NANOS_PER_SECOND = 1000 * 1000 * 1000; + + @Test + public void setAndGetTime() { + TestClock clock = TestClock.create(Timestamp.create(1, 2)); + assertThat(clock.now()).isEqualTo(Timestamp.create(1, 2)); + clock.setTime(Timestamp.create(3, 4)); + assertThat(clock.now()).isEqualTo(Timestamp.create(3, 4)); + } + + @Test + public void advanceTime() { + TestClock clock = TestClock.create(Timestamp.create(1, 500 * 1000 * 1000)); + clock.advanceTime(Duration.create(2, 600 * 1000 * 1000)); + assertThat(clock.now()).isEqualTo(Timestamp.create(4, 100 * 1000 * 1000)); + } + + @Test + public void measureElapsedTime() { + TestClock clock = TestClock.create(Timestamp.create(10, 1)); + long nanos1 = clock.nowNanos(); + clock.setTime(Timestamp.create(11, 5)); + long nanos2 = clock.nowNanos(); + assertThat(nanos2 - nanos1).isEqualTo(1000 * 1000 * 1000 + 4); + } + + @Test(expected = ArithmeticException.class) + public void catchOverflow() { + TestClock.create(Timestamp.create(Long.MAX_VALUE / NUM_NANOS_PER_SECOND + 1, 0)); + } + + @Test(expected = ArithmeticException.class) + public void catchNegativeOverflow() { + TestClock.create(Timestamp.create(Long.MIN_VALUE / NUM_NANOS_PER_SECOND - 1, 0)); + } +} diff --git a/api/src/test/java/io/opencensus/tags/TagKeyTest.java b/api/src/test/java/io/opencensus/tags/TagKeyTest.java new file mode 100644 index 00000000..aed02d3f --- /dev/null +++ b/api/src/test/java/io/opencensus/tags/TagKeyTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.testing.EqualsTester; +import io.opencensus.tags.TagKey.TagType; +import java.util.Arrays; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link TagKey} */ +@RunWith(JUnit4.class) +public final class TagKeyTest { + + @Rule public final ExpectedException thrown = ExpectedException.none(); + + @Test + public void testGetName() { + assertThat(TagKey.createStringKey("foo").getName()).isEqualTo("foo"); + } + + @Test + public void testGetTagType() { + assertThat(TagKey.createStringKey("key").getTagType()).isEqualTo(TagType.TAG_STRING); + assertThat(TagKey.createLongKey("key").getTagType()).isEqualTo(TagType.TAG_LONG); + assertThat(TagKey.createBooleanKey("key").getTagType()).isEqualTo(TagType.TAG_BOOLEAN); + } + + @Test + public void createString_AllowTagKeyNameWithMaxLength() { + char[] key = new char[TagKey.MAX_LENGTH]; + Arrays.fill(key, 'k'); + TagKey.createStringKey(new String(key)); + } + + @Test + public void createString_DisallowTagKeyNameOverMaxLength() { + char[] key = new char[TagKey.MAX_LENGTH + 1]; + Arrays.fill(key, 'k'); + thrown.expect(IllegalArgumentException.class); + TagKey.createStringKey(new String(key)); + } + + @Test + public void createString_DisallowUnprintableChars() { + thrown.expect(IllegalArgumentException.class); + TagKey.createStringKey("\2ab\3cd"); + } + + @Test + public void testTagKeyEquals() { + new EqualsTester() + .addEqualityGroup(TagKey.createStringKey("foo"), TagKey.createStringKey("foo")) + .addEqualityGroup(TagKey.createLongKey("foo")) + .addEqualityGroup(TagKey.createBooleanKey("foo")) + .addEqualityGroup(TagKey.createStringKey("bar")) + .testEquals(); + } +} diff --git a/api/src/test/java/io/opencensus/tags/TagMapTest.java b/api/src/test/java/io/opencensus/tags/TagMapTest.java new file mode 100644 index 00000000..41b3b247 --- /dev/null +++ b/api/src/test/java/io/opencensus/tags/TagMapTest.java @@ -0,0 +1,114 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableMap; +import java.util.Arrays; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link TagMap}. */ +// TODO(sebright): Add more tests once the API is finalized. +@RunWith(JUnit4.class) +public class TagMapTest { + + @Rule public final ExpectedException thrown = ExpectedException.none(); + + private static final TagKey<String> KS1 = TagKey.createStringKey("k1"); + private static final TagKey<String> KS2 = TagKey.createStringKey("k2"); + + @Test + public void applyBuilderOperationsInOrder() { + assertThat(newBuilder().set(KS1, "v1").set(KS1, "v2").build().getTags()) + .containsExactly(KS1, "v2"); + } + + @Test + public void allowMutlipleKeysWithSameNameButDifferentTypes() { + TagKey<String> stringKey = TagKey.createStringKey("key"); + TagKey<Long> longKey = TagKey.createLongKey("key"); + TagKey<Boolean> booleanKey = TagKey.createBooleanKey("key"); + assertThat( + newBuilder() + .set(stringKey, "value") + .set(longKey, 123) + .set(booleanKey, true) + .build() + .getTags()) + .containsExactly(stringKey, "value", longKey, 123L, booleanKey, true); + } + + @Test + public void testSet() { + TagMap tags = singletonTagMap(KS1, "v1"); + assertThat(tags.toBuilder().set(KS1, "v2").build().getTags()).containsExactly(KS1, "v2"); + assertThat(tags.toBuilder().set(KS2, "v2").build().getTags()) + .containsExactly(KS1, "v1", KS2, "v2"); + } + + @Test + public void testClear() { + TagMap tags = singletonTagMap(KS1, "v1"); + assertThat(tags.toBuilder().clear(KS1).build().getTags()).isEmpty(); + assertThat(tags.toBuilder().clear(KS2).build().getTags()).containsExactly(KS1, "v1"); + } + + @Test + public void allowStringTagValueWithMaxLength() { + char[] chars = new char[TagMap.MAX_STRING_LENGTH]; + Arrays.fill(chars, 'v'); + String value = new String(chars); + TagKey<String> key = TagKey.createStringKey("K"); + newBuilder().set(key, value); + } + + @Test + public void disallowStringTagValueOverMaxLength() { + char[] chars = new char[TagMap.MAX_STRING_LENGTH + 1]; + Arrays.fill(chars, 'v'); + String value = new String(chars); + TagKey<String> key = TagKey.createStringKey("K"); + thrown.expect(IllegalArgumentException.class); + newBuilder().set(key, value); + } + + @Test + public void disallowStringTagValueWithUnprintableChars() { + String value = "\2ab\3cd"; + TagKey<String> key = TagKey.createStringKey("K"); + thrown.expect(IllegalArgumentException.class); + newBuilder().set(key, value); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private final TagKey<Long> badLongKey = (TagKey) TagKey.createStringKey("Key"); + + @Test(expected = IllegalArgumentException.class) + public void disallowSettingWrongTypeOfKey() { + newBuilder().set(badLongKey, 123); + } + + private static TagMap.Builder newBuilder() { + return new TagMap.Builder(); + } + + private static <TagValueT> TagMap singletonTagMap(TagKey<TagValueT> key, TagValueT value) { + return new TagMap(ImmutableMap.<TagKey<?>, Object>of(key, value)); + } +} diff --git a/api/src/test/java/io/opencensus/trace/AnnotationTest.java b/api/src/test/java/io/opencensus/trace/AnnotationTest.java new file mode 100644 index 00000000..fbfe5fd3 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/AnnotationTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.testing.EqualsTester; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link Link}. */ +@RunWith(JUnit4.class) +public class AnnotationTest { + @Test(expected = NullPointerException.class) + public void fromDescription_NullDescription() { + Annotation.fromDescription(null); + } + + @Test + public void fromDescription() { + Annotation annotation = Annotation.fromDescription("MyAnnotationText"); + assertThat(annotation.getDescription()).isEqualTo("MyAnnotationText"); + assertThat(annotation.getAttributes().size()).isEqualTo(0); + } + + @Test(expected = NullPointerException.class) + public void fromDescriptionAndAttributes_NullDescription() { + Annotation.fromDescriptionAndAttributes(null, Collections.<String, AttributeValue>emptyMap()); + } + + @Test(expected = NullPointerException.class) + public void fromDescriptionAndAttributes_NullAttributes() { + Annotation.fromDescriptionAndAttributes("", null); + } + + @Test + public void fromDescriptionAndAttributes() { + Map<String, AttributeValue> attributes = new HashMap<String, AttributeValue>(); + attributes.put( + "MyStringAttributeKey", AttributeValue.stringAttributeValue("MyStringAttributeValue")); + Annotation annotation = Annotation.fromDescriptionAndAttributes("MyAnnotationText", attributes); + assertThat(annotation.getDescription()).isEqualTo("MyAnnotationText"); + assertThat(annotation.getAttributes()).isEqualTo(attributes); + } + + @Test + public void fromDescriptionAndAttributes_EmptyAttributes() { + Annotation annotation = + Annotation.fromDescriptionAndAttributes( + "MyAnnotationText", Collections.<String, AttributeValue>emptyMap()); + assertThat(annotation.getDescription()).isEqualTo("MyAnnotationText"); + assertThat(annotation.getAttributes().size()).isEqualTo(0); + } + + @Test + public void annotation_EqualsAndHashCode() { + EqualsTester tester = new EqualsTester(); + Map<String, AttributeValue> attributes = new HashMap<String, AttributeValue>(); + attributes.put( + "MyStringAttributeKey", AttributeValue.stringAttributeValue("MyStringAttributeValue")); + tester + .addEqualityGroup( + Annotation.fromDescription("MyAnnotationText"), + Annotation.fromDescriptionAndAttributes( + "MyAnnotationText", Collections.<String, AttributeValue>emptyMap())) + .addEqualityGroup( + Annotation.fromDescriptionAndAttributes("MyAnnotationText", attributes), + Annotation.fromDescriptionAndAttributes("MyAnnotationText", attributes)) + .addEqualityGroup(Annotation.fromDescription("MyAnnotationText2")); + tester.testEquals(); + } + + @Test + public void annotation_ToString() { + Annotation annotation = Annotation.fromDescription("MyAnnotationText"); + assertThat(annotation.toString()).contains("MyAnnotationText"); + Map<String, AttributeValue> attributes = new HashMap<String, AttributeValue>(); + attributes.put( + "MyStringAttributeKey", AttributeValue.stringAttributeValue("MyStringAttributeValue")); + annotation = Annotation.fromDescriptionAndAttributes("MyAnnotationText2", attributes); + assertThat(annotation.toString()).contains("MyAnnotationText2"); + assertThat(annotation.toString()).contains(attributes.toString()); + } +} diff --git a/api/src/test/java/io/opencensus/trace/AttributeValueTest.java b/api/src/test/java/io/opencensus/trace/AttributeValueTest.java new file mode 100644 index 00000000..7a999604 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/AttributeValueTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.testing.EqualsTester; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link AttributeValue}. */ +@RunWith(JUnit4.class) +public class AttributeValueTest { + @Test + public void stringAttributeValue() { + AttributeValue attribute = AttributeValue.stringAttributeValue("MyStringAttributeValue"); + assertThat(attribute.getStringValue()).isEqualTo("MyStringAttributeValue"); + assertThat(attribute.getBooleanValue()).isNull(); + assertThat(attribute.getLongValue()).isNull(); + } + + @Test + public void booleanAttributeValue() { + AttributeValue attribute = AttributeValue.booleanAttributeValue(true); + assertThat(attribute.getStringValue()).isNull(); + assertThat(attribute.getBooleanValue()).isTrue(); + assertThat(attribute.getLongValue()).isNull(); + } + + @Test + public void longAttributeValue() { + AttributeValue attribute = AttributeValue.longAttributeValue(123456L); + assertThat(attribute.getStringValue()).isNull(); + assertThat(attribute.getBooleanValue()).isNull(); + assertThat(attribute.getLongValue()).isEqualTo(123456L); + } + + @Test + public void attributeValue_EqualsAndHashCode() { + EqualsTester tester = new EqualsTester(); + tester.addEqualityGroup( + AttributeValue.stringAttributeValue("MyStringAttributeValue"), + AttributeValue.stringAttributeValue("MyStringAttributeValue")); + tester.addEqualityGroup(AttributeValue.stringAttributeValue("MyStringAttributeDiffValue")); + tester.addEqualityGroup( + AttributeValue.booleanAttributeValue(true), AttributeValue.booleanAttributeValue(true)); + tester.addEqualityGroup(AttributeValue.booleanAttributeValue(false)); + tester.addEqualityGroup( + AttributeValue.longAttributeValue(123456L), AttributeValue.longAttributeValue(123456L)); + tester.addEqualityGroup(AttributeValue.longAttributeValue(1234567L)); + tester.testEquals(); + } + + @Test + public void attributeValue_ToString() { + AttributeValue attribute = AttributeValue.stringAttributeValue("MyStringAttributeValue"); + assertThat(attribute.toString()).contains("MyStringAttributeValue"); + attribute = AttributeValue.booleanAttributeValue(true); + assertThat(attribute.toString()).contains("true"); + attribute = AttributeValue.longAttributeValue(123456L); + assertThat(attribute.toString()).contains("123456"); + } +} diff --git a/api/src/test/java/io/opencensus/trace/BinaryPropagationHandlerTest.java b/api/src/test/java/io/opencensus/trace/BinaryPropagationHandlerTest.java new file mode 100644 index 00000000..d75effb6 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/BinaryPropagationHandlerTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import java.text.ParseException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link BinaryPropagationHandler}. */ +@RunWith(JUnit4.class) +public class BinaryPropagationHandlerTest { + private static final BinaryPropagationHandler binaryPropagationHandler = + BinaryPropagationHandler.getNoopBinaryPropagationHandler(); + + @Test(expected = NullPointerException.class) + public void toBinaryValue_NullSpanContext() { + binaryPropagationHandler.toBinaryValue(null); + } + + @Test + public void toBinaryValue_NotNullSpanContext() { + assertThat(binaryPropagationHandler.toBinaryValue(SpanContext.INVALID)).isEqualTo(new byte[0]); + } + + @Test(expected = NullPointerException.class) + public void fromBinaryValue_NullInput() throws ParseException { + binaryPropagationHandler.fromBinaryValue(null); + } + + @Test + public void fromBinaryValue_NotNullInput() throws ParseException { + assertThat(binaryPropagationHandler.fromBinaryValue(new byte[0])) + .isEqualTo(SpanContext.INVALID); + } +} diff --git a/api/src/test/java/io/opencensus/trace/BlankSpanTest.java b/api/src/test/java/io/opencensus/trace/BlankSpanTest.java new file mode 100644 index 00000000..252d02ff --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/BlankSpanTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link BlankSpan}. */ +@RunWith(JUnit4.class) +public class BlankSpanTest { + @Test + public void hasInvalidContextAndDefaultSpanOptions() { + assertThat(BlankSpan.INSTANCE.getContext()).isEqualTo(SpanContext.INVALID); + assertThat(BlankSpan.INSTANCE.getOptions().isEmpty()).isTrue(); + } + + @Test + public void doNotCrash() { + Map<String, AttributeValue> attributes = new HashMap<String, AttributeValue>(); + attributes.put( + "MyStringAttributeKey", AttributeValue.stringAttributeValue("MyStringAttributeValue")); + Map<String, AttributeValue> multipleAttributes = new HashMap<String, AttributeValue>(); + multipleAttributes.put( + "MyStringAttributeKey", AttributeValue.stringAttributeValue("MyStringAttributeValue")); + multipleAttributes.put("MyBooleanAttributeKey", AttributeValue.booleanAttributeValue(true)); + multipleAttributes.put("MyLongAttributeKey", AttributeValue.longAttributeValue(123)); + // Tests only that all the methods are not crashing/throwing errors. + BlankSpan.INSTANCE.addAttributes(attributes); + BlankSpan.INSTANCE.addAttributes(multipleAttributes); + BlankSpan.INSTANCE.addAnnotation("MyAnnotation"); + BlankSpan.INSTANCE.addAnnotation("MyAnnotation", attributes); + BlankSpan.INSTANCE.addAnnotation("MyAnnotation", multipleAttributes); + BlankSpan.INSTANCE.addAnnotation(Annotation.fromDescription("MyAnnotation")); + BlankSpan.INSTANCE.addNetworkEvent(NetworkEvent.builder(NetworkEvent.Type.SENT, 1L).build()); + BlankSpan.INSTANCE.addLink(Link.fromSpanContext(SpanContext.INVALID, Link.Type.CHILD)); + BlankSpan.INSTANCE.end(EndSpanOptions.DEFAULT); + BlankSpan.INSTANCE.end(); + } + + @Test + public void blankSpan_ToString() { + assertThat(BlankSpan.INSTANCE.toString()).isEqualTo("BlankSpan"); + } +} diff --git a/api/src/test/java/io/opencensus/trace/ContextUtilsTest.java b/api/src/test/java/io/opencensus/trace/ContextUtilsTest.java new file mode 100644 index 00000000..e4513d74 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/ContextUtilsTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import io.grpc.Context; +import io.opencensus.common.NonThrowingCloseable; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link ContextUtils}. */ +@RunWith(JUnit4.class) +public class ContextUtilsTest { + @Mock private Span span; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void getCurrentSpan_WhenNoContext() { + assertThat(ContextUtils.getCurrentSpan()).isNull(); + } + + @Test + public void getCurrentSpan() { + assertThat(ContextUtils.getCurrentSpan()).isNull(); + Context origContext = Context.current().withValue(ContextUtils.CONTEXT_SPAN_KEY, span).attach(); + // Make sure context is detached even if test fails. + try { + assertThat(ContextUtils.getCurrentSpan()).isSameAs(span); + } finally { + Context.current().detach(origContext); + } + assertThat(ContextUtils.getCurrentSpan()).isNull(); + } + + @Test + public void withSpan() { + assertThat(ContextUtils.getCurrentSpan()).isNull(); + NonThrowingCloseable ws = ContextUtils.withSpan(span); + try { + assertThat(ContextUtils.getCurrentSpan()).isSameAs(span); + } finally { + ws.close(); + } + assertThat(ContextUtils.getCurrentSpan()).isNull(); + ; + } + + @Test + public void propagationViaRunnable() { + Runnable runnable = null; + NonThrowingCloseable ws = ContextUtils.withSpan(span); + try { + assertThat(ContextUtils.getCurrentSpan()).isSameAs(span); + runnable = + Context.current() + .wrap( + new Runnable() { + @Override + public void run() { + assertThat(ContextUtils.getCurrentSpan()).isSameAs(span); + } + }); + } finally { + ws.close(); + } + assertThat(ContextUtils.getCurrentSpan()).isNotSameAs(span); + // When we run the runnable we will have the span in the current Context. + runnable.run(); + } +} diff --git a/api/src/test/java/io/opencensus/trace/EndSpanOptionsTest.java b/api/src/test/java/io/opencensus/trace/EndSpanOptionsTest.java new file mode 100644 index 00000000..d531fba5 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/EndSpanOptionsTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.testing.EqualsTester; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link EndSpanOptions}. */ +@RunWith(JUnit4.class) +public class EndSpanOptionsTest { + @Test(expected = NullPointerException.class) + public void setNullStatus() { + EndSpanOptions.builder().setStatus(null).build(); + } + + @Test + public void endSpanOptions_DefaultOptions() { + assertThat(EndSpanOptions.DEFAULT.getStatus()).isEqualTo(Status.OK); + } + + @Test + public void setStatus() { + EndSpanOptions endSpanOptions = + EndSpanOptions.builder() + .setStatus(Status.CANCELLED.withDescription("ThisIsAnError")) + .build(); + assertThat(endSpanOptions.getStatus()) + .isEqualTo(Status.CANCELLED.withDescription("ThisIsAnError")); + } + + @Test + public void endSpanOptions_EqualsAndHashCode() { + EqualsTester tester = new EqualsTester(); + tester.addEqualityGroup( + EndSpanOptions.builder() + .setStatus(Status.CANCELLED.withDescription("ThisIsAnError")) + .build(), + EndSpanOptions.builder() + .setStatus(Status.CANCELLED.withDescription("ThisIsAnError")) + .build()); + tester.addEqualityGroup(EndSpanOptions.builder().build(), EndSpanOptions.DEFAULT); + tester.testEquals(); + } + + @Test + public void endSpanOptions_ToString() { + EndSpanOptions endSpanOptions = + EndSpanOptions.builder() + .setStatus(Status.CANCELLED.withDescription("ThisIsAnError")) + .build(); + assertThat(endSpanOptions.toString()).contains("ThisIsAnError"); + } +} diff --git a/api/src/test/java/io/opencensus/trace/LinkTest.java b/api/src/test/java/io/opencensus/trace/LinkTest.java new file mode 100644 index 00000000..e1289f33 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/LinkTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.testing.EqualsTester; +import io.opencensus.trace.Link.Type; +import java.util.Random; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link Link}. */ +@RunWith(JUnit4.class) +public class LinkTest { + private final Random random = new Random(1234); + private final SpanContext spanContext = + SpanContext.create( + TraceId.generateRandomId(random), SpanId.generateRandomId(random), TraceOptions.DEFAULT); + + @Test + public void fromSpanContext_ChildLink() { + Link link = Link.fromSpanContext(spanContext, Type.CHILD); + assertThat(link.getTraceId()).isEqualTo(spanContext.getTraceId()); + assertThat(link.getSpanId()).isEqualTo(spanContext.getSpanId()); + assertThat(link.getType()).isEqualTo(Type.CHILD); + } + + @Test + public void fromSpanContext_ParentLink() { + Link link = Link.fromSpanContext(spanContext, Type.PARENT); + assertThat(link.getTraceId()).isEqualTo(spanContext.getTraceId()); + assertThat(link.getSpanId()).isEqualTo(spanContext.getSpanId()); + assertThat(link.getType()).isEqualTo(Type.PARENT); + } + + @Test + public void link_EqualsAndHashCode() { + EqualsTester tester = new EqualsTester(); + tester + .addEqualityGroup( + Link.fromSpanContext(spanContext, Type.PARENT), + Link.fromSpanContext(spanContext, Type.PARENT)) + .addEqualityGroup( + Link.fromSpanContext(spanContext, Type.CHILD), + Link.fromSpanContext(spanContext, Type.CHILD)) + .addEqualityGroup(Link.fromSpanContext(SpanContext.INVALID, Type.CHILD)) + .addEqualityGroup(Link.fromSpanContext(SpanContext.INVALID, Type.PARENT)); + tester.testEquals(); + } + + @Test + public void link_ToString() { + Link link = Link.fromSpanContext(spanContext, Type.CHILD); + assertThat(link.toString()).contains(spanContext.getTraceId().toString()); + assertThat(link.toString()).contains(spanContext.getSpanId().toString()); + assertThat(link.toString()).contains("CHILD"); + link = Link.fromSpanContext(spanContext, Type.PARENT); + assertThat(link.toString()).contains(spanContext.getTraceId().toString()); + assertThat(link.toString()).contains(spanContext.getSpanId().toString()); + assertThat(link.toString()).contains("PARENT"); + } +} diff --git a/api/src/test/java/io/opencensus/trace/NetworkEventTest.java b/api/src/test/java/io/opencensus/trace/NetworkEventTest.java new file mode 100644 index 00000000..946e8ce1 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/NetworkEventTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.common.Timestamp; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link NetworkEvent}. */ +@RunWith(JUnit4.class) +public class NetworkEventTest { + @Test(expected = NullPointerException.class) + public void buildNetworkEvent_NullType() { + NetworkEvent.builder(null, 1L).build(); + } + + @Test + public void buildNetworkEvent_WithRequiredFields() { + NetworkEvent networkEvent = NetworkEvent.builder(NetworkEvent.Type.SENT, 1L).build(); + assertThat(networkEvent.getType()).isEqualTo(NetworkEvent.Type.SENT); + assertThat(networkEvent.getMessageId()).isEqualTo(1L); + assertThat(networkEvent.getKernelTimestamp()).isNull(); + assertThat(networkEvent.getMessageSize()).isEqualTo(0L); + } + + @Test + public void buildNetworkEvent_WithTimestamp() { + NetworkEvent networkEvent = + NetworkEvent.builder(NetworkEvent.Type.SENT, 1L) + .setKernelTimestamp(Timestamp.fromMillis(123456L)) + .build(); + assertThat(networkEvent.getKernelTimestamp()).isEqualTo(Timestamp.fromMillis(123456L)); + assertThat(networkEvent.getType()).isEqualTo(NetworkEvent.Type.SENT); + assertThat(networkEvent.getMessageId()).isEqualTo(1L); + assertThat(networkEvent.getMessageSize()).isEqualTo(0L); + } + + @Test + public void buildNetworkEvent_WithMessageSize() { + NetworkEvent networkEvent = + NetworkEvent.builder(NetworkEvent.Type.SENT, 1L).setMessageSize(123L).build(); + assertThat(networkEvent.getKernelTimestamp()).isNull(); + assertThat(networkEvent.getType()).isEqualTo(NetworkEvent.Type.SENT); + assertThat(networkEvent.getMessageId()).isEqualTo(1L); + assertThat(networkEvent.getMessageSize()).isEqualTo(123L); + } + + @Test + public void buildNetworkEvent_WithAllValues() { + NetworkEvent networkEvent = + NetworkEvent.builder(NetworkEvent.Type.RECV, 1L) + .setKernelTimestamp(Timestamp.fromMillis(123456L)) + .setMessageSize(123L) + .build(); + assertThat(networkEvent.getKernelTimestamp()).isEqualTo(Timestamp.fromMillis(123456L)); + assertThat(networkEvent.getType()).isEqualTo(NetworkEvent.Type.RECV); + assertThat(networkEvent.getMessageId()).isEqualTo(1L); + assertThat(networkEvent.getMessageSize()).isEqualTo(123L); + } + + @Test + public void networkEvent_ToString() { + NetworkEvent networkEvent = + NetworkEvent.builder(NetworkEvent.Type.SENT, 1L) + .setKernelTimestamp(Timestamp.fromMillis(123456L)) + .setMessageSize(123L) + .build(); + assertThat(networkEvent.toString()).contains(Timestamp.fromMillis(123456L).toString()); + assertThat(networkEvent.toString()).contains("type=SENT"); + assertThat(networkEvent.toString()).contains("messageId=1"); + assertThat(networkEvent.toString()).contains("messageSize=123"); + } +} diff --git a/api/src/test/java/io/opencensus/trace/SamplersTest.java b/api/src/test/java/io/opencensus/trace/SamplersTest.java new file mode 100644 index 00000000..af7fcaf5 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/SamplersTest.java @@ -0,0 +1,167 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.Collections; +import java.util.Random; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link Samplers}. */ +@RunWith(JUnit4.class) +public class SamplersTest { + private final Random random = new Random(1234); + private final TraceId traceId = TraceId.generateRandomId(random); + private final SpanId parentSpanId = SpanId.generateRandomId(random); + private final SpanId spanId = SpanId.generateRandomId(random); + private final SpanContext sampledSpanContext = + SpanContext.create(traceId, parentSpanId, TraceOptions.builder().setIsSampled().build()); + private final SpanContext notSampledSpanContext = + SpanContext.create(traceId, parentSpanId, TraceOptions.DEFAULT); + + @Test + public void alwaysSampleSampler_AlwaysReturnTrue() { + // Sampled parent. + assertThat( + Samplers.alwaysSample() + .shouldSample( + sampledSpanContext, + false, + traceId, + spanId, + "Another name", + Collections.<Span>emptyList())) + .isTrue(); + // Not sampled parent. + assertThat( + Samplers.alwaysSample() + .shouldSample( + notSampledSpanContext, + false, + traceId, + spanId, + "Yet another name", + Collections.<Span>emptyList())) + .isTrue(); + } + + @Test + public void alwaysSampleSampler_ToString() { + assertThat(Samplers.alwaysSample().toString()).isEqualTo("AlwaysSampleSampler"); + } + + @Test + public void neverSampleSampler_AlwaysReturnFalse() { + // Sampled parent. + assertThat( + Samplers.neverSample() + .shouldSample( + sampledSpanContext, + false, + traceId, + spanId, + "bar", + Collections.<Span>emptyList())) + .isFalse(); + // Not sampled parent. + assertThat( + Samplers.neverSample() + .shouldSample( + notSampledSpanContext, + false, + traceId, + spanId, + "quux", + Collections.<Span>emptyList())) + .isFalse(); + } + + @Test + public void neverSampleSampler_ToString() { + assertThat(Samplers.neverSample().toString()).isEqualTo("NeverSampleSampler"); + } + + @Test(expected = IllegalArgumentException.class) + public void probabilitySampler_outOfRangeHighProbability() { + Samplers.probabilitySampler(1.01); + } + + @Test(expected = IllegalArgumentException.class) + public void probabilitySampler_outOfRangeLowProbability() { + Samplers.probabilitySampler(-0.00001); + } + + private final void probabilitySampler_AlwaysReturnTrueForSampled(Sampler sampler) { + final int numSamples = 100; // Number of traces for which to generate sampling decisions. + for (int i = 0; i < numSamples; i++) { + assertThat( + sampler.shouldSample( + sampledSpanContext, + false, + TraceId.generateRandomId(random), + spanId, + "bar", + Collections.<Span>emptyList())) + .isTrue(); + } + } + + private final void probabilitySampler_SamplesWithProbabilityForUnsampled( + Sampler sampler, double probability) { + final int numSamples = 1000; // Number of traces for which to generate sampling decisions. + int count = 0; // Count of spans with sampling enabled + for (int i = 0; i < numSamples; i++) { + if (sampler.shouldSample( + notSampledSpanContext, + false, + TraceId.generateRandomId(random), + spanId, + "bar", + Collections.<Span>emptyList())) { + count++; + } + } + double proportionSampled = (double) count / numSamples; + // Allow for a large amount of slop (+/- 10%) in number of sampled traces, to avoid flakiness. + assertThat(proportionSampled < probability + 0.1 && proportionSampled > probability - 0.1) + .isTrue(); + } + + @Test + public void probabilitySamper_SamplesWithProbability() { + final Sampler neverSample = Samplers.probabilitySampler(0.0); + probabilitySampler_AlwaysReturnTrueForSampled(neverSample); + probabilitySampler_SamplesWithProbabilityForUnsampled(neverSample, 0.0); + final Sampler alwaysSample = Samplers.probabilitySampler(1.0); + probabilitySampler_AlwaysReturnTrueForSampled(alwaysSample); + probabilitySampler_SamplesWithProbabilityForUnsampled(alwaysSample, 1.0); + final Sampler fiftyPercentSample = Samplers.probabilitySampler(0.5); + probabilitySampler_AlwaysReturnTrueForSampled(fiftyPercentSample); + probabilitySampler_SamplesWithProbabilityForUnsampled(fiftyPercentSample, 0.5); + final Sampler twentyPercentSample = Samplers.probabilitySampler(0.2); + probabilitySampler_AlwaysReturnTrueForSampled(twentyPercentSample); + probabilitySampler_SamplesWithProbabilityForUnsampled(twentyPercentSample, 0.2); + final Sampler twoThirdsSample = Samplers.probabilitySampler(2.0 / 3.0); + probabilitySampler_AlwaysReturnTrueForSampled(twoThirdsSample); + probabilitySampler_SamplesWithProbabilityForUnsampled(twoThirdsSample, 2.0 / 3.0); + } + + @Test + public void probabilitySampler_ToString() { + assertThat((Samplers.probabilitySampler(0.5)).toString()).contains("0.5"); + } +} diff --git a/api/src/test/java/io/opencensus/trace/ScopedSpanHandleTest.java b/api/src/test/java/io/opencensus/trace/ScopedSpanHandleTest.java new file mode 100644 index 00000000..8922f4c2 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/ScopedSpanHandleTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.verify; + +import io.opencensus.common.NonThrowingCloseable; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link ScopedSpanHandle}. */ +@RunWith(JUnit4.class) +public class ScopedSpanHandleTest { + private static final Tracer tracer = Tracer.getNoopTracer(); + @Mock private Span span; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void initAttachesSpan_CloseDetachesAndEndsSpan() { + assertThat(tracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE); + NonThrowingCloseable ss = new ScopedSpanHandle(span); + try { + assertThat(tracer.getCurrentSpan()).isSameAs(span); + } finally { + ss.close(); + } + assertThat(tracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE); + verify(span).end(same(EndSpanOptions.DEFAULT)); + } +} diff --git a/api/src/test/java/io/opencensus/trace/SpanBuilderTest.java b/api/src/test/java/io/opencensus/trace/SpanBuilderTest.java new file mode 100644 index 00000000..ccd3d806 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/SpanBuilderTest.java @@ -0,0 +1,170 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.opencensus.common.NonThrowingCloseable; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link Tracer}. */ +@RunWith(JUnit4.class) +public class SpanBuilderTest { + private static final String SPAN_NAME = "MySpanName"; + private static final Tracer tracer = Tracing.getTracer(); + private final Random random = new Random(1234); + private final SpanContext spanContext = + SpanContext.create( + TraceId.generateRandomId(random), SpanId.generateRandomId(random), TraceOptions.DEFAULT); + @Mock private Span span; + @Mock private SpanFactory spanFactory; + private SpanBuilder spanBuilder; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + spanBuilder = SpanBuilder.builder(spanFactory, BlankSpan.INSTANCE, SPAN_NAME); + } + + @Test + public void startScopedSpanRoot() { + when(spanFactory.startSpan(isNull(Span.class), same(SPAN_NAME), eq(new StartSpanOptions()))) + .thenReturn(span); + NonThrowingCloseable ss = spanBuilder.becomeRoot().startScopedSpan(); + try { + assertThat(tracer.getCurrentSpan()).isSameAs(span); + } finally { + ss.close(); + } + verify(span).end(same(EndSpanOptions.DEFAULT)); + } + + @Test + public void startScopedSpanRootWithOptions() { + StartSpanOptions startSpanOptions = new StartSpanOptions(); + startSpanOptions.setSampler(Samplers.neverSample()); + when(spanFactory.startSpan(isNull(Span.class), same(SPAN_NAME), eq(startSpanOptions))) + .thenReturn(span); + NonThrowingCloseable ss = + spanBuilder.becomeRoot().setSampler(Samplers.neverSample()).startScopedSpan(); + try { + assertThat(tracer.getCurrentSpan()).isSameAs(span); + } finally { + ss.close(); + } + verify(span).end(same(EndSpanOptions.DEFAULT)); + } + + @Test + public void startRootSpan() { + when(spanFactory.startSpan(isNull(Span.class), same(SPAN_NAME), eq(new StartSpanOptions()))) + .thenReturn(span); + Span rootSpan = spanBuilder.becomeRoot().startSpan(); + assertThat(rootSpan).isEqualTo(span); + rootSpan.end(); + verify(span).end(same(EndSpanOptions.DEFAULT)); + } + + @Test + public void startSpan_WithNullParent() { + spanBuilder = SpanBuilder.builder(spanFactory, null, SPAN_NAME); + when(spanFactory.startSpan(isNull(Span.class), same(SPAN_NAME), eq(new StartSpanOptions()))) + .thenReturn(span); + Span rootSpan = spanBuilder.startSpan(); + assertThat(rootSpan).isEqualTo(span); + rootSpan.end(); + verify(span).end(same(EndSpanOptions.DEFAULT)); + } + + @Test + public void startRootSpanWithOptions() { + List<Span> parentList = Arrays.<Span>asList(BlankSpan.INSTANCE); + StartSpanOptions startSpanOptions = new StartSpanOptions(); + startSpanOptions.setParentLinks(parentList); + startSpanOptions.setSampler(Samplers.neverSample()); + when(spanFactory.startSpan(isNull(Span.class), same(SPAN_NAME), eq(startSpanOptions))) + .thenReturn(span); + Span rootSpan = + spanBuilder + .becomeRoot() + .setSampler(Samplers.neverSample()) + .setParentLinks(parentList) + .startSpan(); + assertThat(rootSpan).isEqualTo(span); + rootSpan.end(); + verify(span).end(same(EndSpanOptions.DEFAULT)); + } + + @Test + public void startChildSpan() { + when(spanFactory.startSpan( + same(BlankSpan.INSTANCE), same(SPAN_NAME), eq(new StartSpanOptions()))) + .thenReturn(span); + Span childSpan = spanBuilder.startSpan(); + assertThat(childSpan).isEqualTo(span); + childSpan.end(); + verify(span).end(same(EndSpanOptions.DEFAULT)); + } + + @Test + public void startChildSpanWithOptions() { + StartSpanOptions startSpanOptions = new StartSpanOptions(); + startSpanOptions.setSampler(Samplers.neverSample()); + startSpanOptions.setRecordEvents(true); + when(spanFactory.startSpan(same(BlankSpan.INSTANCE), same(SPAN_NAME), eq(startSpanOptions))) + .thenReturn(span); + Span childSpan = + spanBuilder.setSampler(Samplers.neverSample()).setRecordEvents(true).startSpan(); + assertThat(childSpan).isEqualTo(span); + childSpan.end(); + verify(span).end(same(EndSpanOptions.DEFAULT)); + } + + @Test + public void startSpanWitRemoteParent() { + spanBuilder = SpanBuilder.builderWithRemoteParent(spanFactory, spanContext, SPAN_NAME); + when(spanFactory.startSpanWithRemoteParent( + same(spanContext), same(SPAN_NAME), eq(new StartSpanOptions()))) + .thenReturn(span); + Span remoteChildSpan = spanBuilder.startSpan(); + assertThat(remoteChildSpan).isEqualTo(span); + remoteChildSpan.end(); + verify(span).end(same(EndSpanOptions.DEFAULT)); + } + + @Test + public void startSpanWitRemoteParent_WithNullParent() { + spanBuilder = SpanBuilder.builderWithRemoteParent(spanFactory, null, SPAN_NAME); + when(spanFactory.startSpanWithRemoteParent( + isNull(SpanContext.class), same(SPAN_NAME), eq(new StartSpanOptions()))) + .thenReturn(span); + Span remoteChildSpan = spanBuilder.startSpan(); + assertThat(remoteChildSpan).isEqualTo(span); + remoteChildSpan.end(); + verify(span).end(same(EndSpanOptions.DEFAULT)); + } +} diff --git a/api/src/test/java/io/opencensus/trace/SpanContextTest.java b/api/src/test/java/io/opencensus/trace/SpanContextTest.java new file mode 100644 index 00000000..c0eb5d40 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/SpanContextTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.testing.EqualsTester; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link SpanContext}. */ +@RunWith(JUnit4.class) +public class SpanContextTest { + private static final byte[] firstTraceIdBytes = + new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'a'}; + private static final byte[] secondTraceIdBytes = + new byte[] {0, 0, 0, 0, 0, 0, 0, '0', 0, 0, 0, 0, 0, 0, 0, 0}; + private static final byte[] firstSpanIdBytes = new byte[] {0, 0, 0, 0, 0, 0, 0, 'a'}; + private static final byte[] secondSpanIdBytes = new byte[] {'0', 0, 0, 0, 0, 0, 0, 0}; + private static final SpanContext first = + SpanContext.create( + TraceId.fromBytes(firstTraceIdBytes), + SpanId.fromBytes(firstSpanIdBytes), + TraceOptions.DEFAULT); + private static final SpanContext second = + SpanContext.create( + TraceId.fromBytes(secondTraceIdBytes), + SpanId.fromBytes(secondSpanIdBytes), + TraceOptions.builder().setIsSampled().build()); + + @Test + public void invalidSpanContext() { + assertThat(SpanContext.INVALID.getTraceId()).isEqualTo(TraceId.INVALID); + assertThat(SpanContext.INVALID.getSpanId()).isEqualTo(SpanId.INVALID); + assertThat(SpanContext.INVALID.getTraceOptions()).isEqualTo(TraceOptions.DEFAULT); + } + + @Test + public void isValid() { + assertThat(SpanContext.INVALID.isValid()).isFalse(); + assertThat( + SpanContext.create( + TraceId.fromBytes(firstTraceIdBytes), SpanId.INVALID, TraceOptions.DEFAULT) + .isValid()) + .isFalse(); + assertThat( + SpanContext.create( + TraceId.INVALID, SpanId.fromBytes(firstSpanIdBytes), TraceOptions.DEFAULT) + .isValid()) + .isFalse(); + assertThat(first.isValid()).isTrue(); + assertThat(second.isValid()).isTrue(); + } + + @Test + public void getTraceId() { + assertThat(first.getTraceId()).isEqualTo(TraceId.fromBytes(firstTraceIdBytes)); + assertThat(second.getTraceId()).isEqualTo(TraceId.fromBytes(secondTraceIdBytes)); + } + + @Test + public void getSpanId() { + assertThat(first.getSpanId()).isEqualTo(SpanId.fromBytes(firstSpanIdBytes)); + assertThat(second.getSpanId()).isEqualTo(SpanId.fromBytes(secondSpanIdBytes)); + } + + @Test + public void getTraceOptions() { + assertThat(first.getTraceOptions()).isEqualTo(TraceOptions.DEFAULT); + assertThat(second.getTraceOptions()).isEqualTo(TraceOptions.builder().setIsSampled().build()); + } + + @Test + public void spanContext_EqualsAndHashCode() { + EqualsTester tester = new EqualsTester(); + tester.addEqualityGroup( + first, + SpanContext.create( + TraceId.fromBytes(firstTraceIdBytes), + SpanId.fromBytes(firstSpanIdBytes), + TraceOptions.DEFAULT)); + tester.addEqualityGroup( + second, + SpanContext.create( + TraceId.fromBytes(secondTraceIdBytes), + SpanId.fromBytes(secondSpanIdBytes), + TraceOptions.builder().setIsSampled().build())); + tester.testEquals(); + } + + @Test + public void spanContext_ToString() { + assertThat(first.toString()).contains(TraceId.fromBytes(firstTraceIdBytes).toString()); + assertThat(first.toString()).contains(SpanId.fromBytes(firstSpanIdBytes).toString()); + assertThat(first.toString()).contains(TraceOptions.DEFAULT.toString()); + assertThat(second.toString()).contains(TraceId.fromBytes(secondTraceIdBytes).toString()); + assertThat(second.toString()).contains(SpanId.fromBytes(secondSpanIdBytes).toString()); + assertThat(second.toString()) + .contains(TraceOptions.builder().setIsSampled().build().toString()); + } +} diff --git a/api/src/test/java/io/opencensus/trace/SpanDataTest.java b/api/src/test/java/io/opencensus/trace/SpanDataTest.java new file mode 100644 index 00000000..7a7f7899 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/SpanDataTest.java @@ -0,0 +1,241 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.testing.EqualsTester; +import io.opencensus.common.Timestamp; +import io.opencensus.trace.Link.Type; +import io.opencensus.trace.SpanData.Attributes; +import io.opencensus.trace.SpanData.Links; +import io.opencensus.trace.SpanData.TimedEvent; +import io.opencensus.trace.SpanData.TimedEvents; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link SpanData}. */ +@RunWith(JUnit4.class) +public class SpanDataTest { + private static final Timestamp startTimestamp = Timestamp.create(123, 456); + private static final Timestamp eventTimestamp1 = Timestamp.create(123, 457); + private static final Timestamp eventTimestamp2 = Timestamp.create(123, 458); + private static final Timestamp eventTimestamp3 = Timestamp.create(123, 459); + private static final Timestamp endTimestamp = Timestamp.create(123, 460); + private static final String DISPLAY_NAME = "MySpanDisplayName"; + private static final String ANNOTATION_TEXT = "MyAnnotationText"; + private static final Annotation annotation = Annotation.fromDescription(ANNOTATION_TEXT); + private static final NetworkEvent recvNetworkEvent = + NetworkEvent.builder(NetworkEvent.Type.RECV, 1).build(); + private static final NetworkEvent sentNetworkEvent = + NetworkEvent.builder(NetworkEvent.Type.SENT, 1).build(); + private static final Status status = Status.DEADLINE_EXCEEDED.withDescription("TooSlow"); + private final Random random = new Random(1234); + private final SpanContext spanContext = + SpanContext.create( + TraceId.generateRandomId(random), SpanId.generateRandomId(random), TraceOptions.DEFAULT); + private final SpanId parentSpanId = SpanId.generateRandomId(random); + private final Map<String, AttributeValue> attributesMap = new HashMap<String, AttributeValue>(); + private final List<TimedEvent<Annotation>> annotationsList = + new LinkedList<TimedEvent<Annotation>>(); + private final List<TimedEvent<NetworkEvent>> networkEventsList = + new LinkedList<SpanData.TimedEvent<NetworkEvent>>(); + private final List<Link> linksList = new LinkedList<Link>(); + private Attributes attributes; + private TimedEvents<Annotation> annotations; + private TimedEvents<NetworkEvent> networkEvents; + private Links links; + + @Before + public void setUp() { + attributesMap.put("MyAttributeKey1", AttributeValue.longAttributeValue(10)); + attributesMap.put("MyAttributeKey2", AttributeValue.booleanAttributeValue(true)); + attributes = Attributes.create(attributesMap, 1); + annotationsList.add(SpanData.TimedEvent.create(eventTimestamp1, annotation)); + annotationsList.add(SpanData.TimedEvent.create(eventTimestamp3, annotation)); + annotations = TimedEvents.create(annotationsList, 2); + networkEventsList.add(SpanData.TimedEvent.create(eventTimestamp1, recvNetworkEvent)); + networkEventsList.add(SpanData.TimedEvent.create(eventTimestamp2, sentNetworkEvent)); + networkEvents = TimedEvents.create(networkEventsList, 3); + linksList.add(Link.fromSpanContext(spanContext, Type.CHILD)); + links = Links.create(linksList, 0); + } + + @Test + public void spanData_AllValues() { + SpanData spanData = + SpanData.create( + spanContext, + parentSpanId, + true, + DISPLAY_NAME, + startTimestamp, + attributes, + annotations, + networkEvents, + links, + status, + endTimestamp); + assertThat(spanData.getContext()).isEqualTo(spanContext); + assertThat(spanData.getParentSpanId()).isEqualTo(parentSpanId); + assertThat(spanData.getHasRemoteParent()).isTrue(); + assertThat(spanData.getDisplayName()).isEqualTo(DISPLAY_NAME); + assertThat(spanData.getStartTimestamp()).isEqualTo(startTimestamp); + assertThat(spanData.getAttributes()).isEqualTo(attributes); + assertThat(spanData.getAnnotations()).isEqualTo(annotations); + assertThat(spanData.getNetworkEvents()).isEqualTo(networkEvents); + assertThat(spanData.getLinks()).isEqualTo(links); + assertThat(spanData.getStatus()).isEqualTo(status); + assertThat(spanData.getEndTimestamp()).isEqualTo(endTimestamp); + } + + @Test + public void spanData_RootActiveSpan() { + SpanData spanData = + SpanData.create( + spanContext, + null, + false, + DISPLAY_NAME, + startTimestamp, + attributes, + annotations, + networkEvents, + links, + null, + null); + assertThat(spanData.getContext()).isEqualTo(spanContext); + assertThat(spanData.getParentSpanId()).isNull(); + assertThat(spanData.getHasRemoteParent()).isFalse(); + assertThat(spanData.getDisplayName()).isEqualTo(DISPLAY_NAME); + assertThat(spanData.getStartTimestamp()).isEqualTo(startTimestamp); + assertThat(spanData.getAttributes()).isEqualTo(attributes); + assertThat(spanData.getAnnotations()).isEqualTo(annotations); + assertThat(spanData.getNetworkEvents()).isEqualTo(networkEvents); + assertThat(spanData.getLinks()).isEqualTo(links); + assertThat(spanData.getStatus()).isNull(); + assertThat(spanData.getEndTimestamp()).isNull(); + } + + @Test + public void spanData_AllDataEmpty() { + SpanData spanData = + SpanData.create( + spanContext, + parentSpanId, + false, + DISPLAY_NAME, + startTimestamp, + Attributes.create(Collections.<String, AttributeValue>emptyMap(), 0), + TimedEvents.create(Collections.<SpanData.TimedEvent<Annotation>>emptyList(), 0), + TimedEvents.create(Collections.<SpanData.TimedEvent<NetworkEvent>>emptyList(), 0), + Links.create(Collections.<Link>emptyList(), 0), + status, + endTimestamp); + assertThat(spanData.getContext()).isEqualTo(spanContext); + assertThat(spanData.getParentSpanId()).isEqualTo(parentSpanId); + assertThat(spanData.getHasRemoteParent()).isFalse(); + assertThat(spanData.getDisplayName()).isEqualTo(DISPLAY_NAME); + assertThat(spanData.getStartTimestamp()).isEqualTo(startTimestamp); + assertThat(spanData.getAttributes().getAttributeMap().isEmpty()).isTrue(); + assertThat(spanData.getAnnotations().getEvents().isEmpty()).isTrue(); + assertThat(spanData.getNetworkEvents().getEvents().isEmpty()).isTrue(); + assertThat(spanData.getLinks().getLinks().isEmpty()).isTrue(); + assertThat(spanData.getStatus()).isEqualTo(status); + assertThat(spanData.getEndTimestamp()).isEqualTo(endTimestamp); + } + + @Test + public void spanDataEquals() { + SpanData allSpanData1 = + SpanData.create( + spanContext, + parentSpanId, + false, + DISPLAY_NAME, + startTimestamp, + attributes, + annotations, + networkEvents, + links, + status, + endTimestamp); + SpanData allSpanData2 = + SpanData.create( + spanContext, + parentSpanId, + false, + DISPLAY_NAME, + startTimestamp, + attributes, + annotations, + networkEvents, + links, + status, + endTimestamp); + SpanData emptySpanData = + SpanData.create( + spanContext, + parentSpanId, + false, + DISPLAY_NAME, + startTimestamp, + Attributes.create(Collections.<String, AttributeValue>emptyMap(), 0), + TimedEvents.create(Collections.<SpanData.TimedEvent<Annotation>>emptyList(), 0), + TimedEvents.create(Collections.<SpanData.TimedEvent<NetworkEvent>>emptyList(), 0), + Links.create(Collections.<Link>emptyList(), 0), + status, + endTimestamp); + new EqualsTester() + .addEqualityGroup(allSpanData1, allSpanData2) + .addEqualityGroup(emptySpanData) + .testEquals(); + } + + @Test + public void spanData_ToString() { + String spanDataString = + SpanData.create( + spanContext, + parentSpanId, + false, + DISPLAY_NAME, + startTimestamp, + attributes, + annotations, + networkEvents, + links, + status, + endTimestamp) + .toString(); + assertThat(spanDataString).contains(spanContext.toString()); + assertThat(spanDataString).contains(parentSpanId.toString()); + assertThat(spanDataString).contains(DISPLAY_NAME); + assertThat(spanDataString).contains(startTimestamp.toString()); + assertThat(spanDataString).contains(attributes.toString()); + assertThat(spanDataString).contains(annotations.toString()); + assertThat(spanDataString).contains(networkEvents.toString()); + assertThat(spanDataString).contains(links.toString()); + assertThat(spanDataString).contains(status.toString()); + assertThat(spanDataString).contains(endTimestamp.toString()); + } +} diff --git a/api/src/test/java/io/opencensus/trace/SpanIdTest.java b/api/src/test/java/io/opencensus/trace/SpanIdTest.java new file mode 100644 index 00000000..a35fddce --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/SpanIdTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.testing.EqualsTester; +import java.util.Arrays; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link SpanId}. */ +@RunWith(JUnit4.class) +public class SpanIdTest { + private static final byte[] firstBytes = new byte[] {0, 0, 0, 0, 0, 0, 0, 'a'}; + private static final byte[] secondBytes = new byte[] {(byte) 0xFF, 0, 0, 0, 0, 0, 0, 0}; + private static final SpanId first = SpanId.fromBytes(firstBytes); + private static final SpanId second = SpanId.fromBytes(secondBytes); + + @Test + public void invalidSpanId() { + assertThat(SpanId.INVALID.getBytes()).isEqualTo(new byte[8]); + } + + @Test + public void isValid() { + assertThat(SpanId.INVALID.isValid()).isFalse(); + assertThat(first.isValid()).isTrue(); + assertThat(second.isValid()).isTrue(); + } + + @Test + public void getBytes() { + assertThat(first.getBytes()).isEqualTo(firstBytes); + assertThat(second.getBytes()).isEqualTo(secondBytes); + } + + @Test + public void traceId_CompareTo() { + assertThat(first.compareTo(second)).isGreaterThan(0); + assertThat(second.compareTo(first)).isLessThan(0); + assertThat(first.compareTo(SpanId.fromBytes(firstBytes))).isEqualTo(0); + } + + @Test + public void traceId_EqualsAndHashCode() { + EqualsTester tester = new EqualsTester(); + tester.addEqualityGroup(SpanId.INVALID, SpanId.INVALID); + tester.addEqualityGroup(first, SpanId.fromBytes(Arrays.copyOf(firstBytes, firstBytes.length))); + tester.addEqualityGroup( + second, SpanId.fromBytes(Arrays.copyOf(secondBytes, secondBytes.length))); + tester.testEquals(); + } + + @Test + public void traceId_ToString() { + assertThat(SpanId.INVALID.toString()).contains("0000000000000000"); + assertThat(first.toString()).contains("0000000000000061"); + assertThat(second.toString()).contains("ff00000000000000"); + } +} diff --git a/api/src/test/java/io/opencensus/trace/SpanTest.java b/api/src/test/java/io/opencensus/trace/SpanTest.java new file mode 100644 index 00000000..c8f7cb50 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/SpanTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.verify; + +import java.util.EnumSet; +import java.util.Map; +import java.util.Random; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mockito; + +/** Unit tests for {@link Span}. */ +@RunWith(JUnit4.class) +public class SpanTest { + private Random random; + private SpanContext spanContext; + private SpanContext notSampledSpanContext; + private EnumSet<Span.Options> spanOptions; + + @Before + public void setUp() { + random = new Random(1234); + spanContext = + SpanContext.create( + TraceId.generateRandomId(random), + SpanId.generateRandomId(random), + TraceOptions.builder().setIsSampled().build()); + notSampledSpanContext = + SpanContext.create( + TraceId.generateRandomId(random), + SpanId.generateRandomId(random), + TraceOptions.DEFAULT); + spanOptions = EnumSet.of(Span.Options.RECORD_EVENTS); + } + + @Test(expected = NullPointerException.class) + public void newSpan_WithNullContext() { + new NoopSpan(null, null); + } + + @Test(expected = IllegalArgumentException.class) + public void newSpan_SampledContextAndNullOptions() { + new NoopSpan(spanContext, null); + } + + @Test(expected = IllegalArgumentException.class) + public void newSpan_SampledContextAndEmptyOptions() { + new NoopSpan(spanContext, EnumSet.noneOf(Span.Options.class)); + } + + @Test + public void getOptions_WhenNullOptions() { + Span span = new NoopSpan(notSampledSpanContext, null); + assertThat(span.getOptions()).isEmpty(); + } + + @Test + public void getContextAndOptions() { + Span span = new NoopSpan(spanContext, spanOptions); + assertThat(span.getContext()).isEqualTo(spanContext); + assertThat(span.getOptions()).isEqualTo(spanOptions); + } + + @Test + public void endCallsEndWithDefaultOptions() { + Span span = Mockito.spy(new NoopSpan(spanContext, spanOptions)); + span.end(); + verify(span).end(same(EndSpanOptions.DEFAULT)); + } + + // No-op implementation of the Span for testing only. + private static class NoopSpan extends Span { + private NoopSpan(SpanContext context, EnumSet<Span.Options> options) { + super(context, options); + } + + @Override + public void addAttributes(Map<String, AttributeValue> attributes) {} + + @Override + public void addAnnotation(String description, Map<String, AttributeValue> attributes) {} + + @Override + public void addAnnotation(Annotation annotation) {} + + @Override + public void addNetworkEvent(NetworkEvent networkEvent) {} + + @Override + public void addLink(Link link) {} + + @Override + public void end(EndSpanOptions options) {} + } +} diff --git a/api/src/test/java/io/opencensus/trace/StartSpanOptionsTest.java b/api/src/test/java/io/opencensus/trace/StartSpanOptionsTest.java new file mode 100644 index 00000000..b91e78ee --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/StartSpanOptionsTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.testing.EqualsTester; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link StartSpanOptions}. */ +@RunWith(JUnit4.class) +public class StartSpanOptionsTest { + private final List<Span> singleParentList = Arrays.<Span>asList(BlankSpan.INSTANCE); + + @Test + public void defaultOptions() { + StartSpanOptions defaultOptions = new StartSpanOptions(); + assertThat(defaultOptions.getSampler()).isNull(); + assertThat(defaultOptions.getParentLinks().isEmpty()).isTrue(); + assertThat(defaultOptions.getRecordEvents()).isNull(); + } + + @Test + public void setSampler() { + StartSpanOptions options = new StartSpanOptions(); + options.setSampler(Samplers.neverSample()); + assertThat(options.getSampler()).isEqualTo(Samplers.neverSample()); + assertThat(options.getParentLinks().isEmpty()).isTrue(); + assertThat(options.getRecordEvents()).isNull(); + } + + @Test + public void setParentLinks() { + StartSpanOptions options = new StartSpanOptions(); + options.setParentLinks(singleParentList); + assertThat(options.getSampler()).isNull(); + assertThat(options.getParentLinks()).isEqualTo(singleParentList); + assertThat(options.getRecordEvents()).isNull(); + } + + @Test + public void setParentLinks_EmptyList() { + StartSpanOptions options = new StartSpanOptions(); + options.setParentLinks(new LinkedList<Span>()); + assertThat(options.getSampler()).isNull(); + assertThat(options.getParentLinks().size()).isEqualTo(0); + assertThat(options.getRecordEvents()).isNull(); + } + + @Test + public void setParentLinks_MultipleParents() { + StartSpanOptions options = new StartSpanOptions(); + options.setParentLinks(Arrays.<Span>asList(BlankSpan.INSTANCE, BlankSpan.INSTANCE)); + assertThat(options.getSampler()).isNull(); + assertThat(options.getParentLinks().size()).isEqualTo(2); + assertThat(options.getRecordEvents()).isNull(); + } + + @Test + public void setRecordEvents() { + StartSpanOptions options = new StartSpanOptions(); + options.setRecordEvents(true); + assertThat(options.getSampler()).isNull(); + assertThat(options.getParentLinks().isEmpty()).isTrue(); + assertThat(options.getRecordEvents()).isTrue(); + } + + @Test + public void setAllProperties() { + StartSpanOptions options = new StartSpanOptions(); + options.setSampler(Samplers.neverSample()); + options.setSampler(Samplers.alwaysSample()); // second SetSampler should apply + options.setRecordEvents(true); + options.setParentLinks(singleParentList); + assertThat(options.getSampler()).isEqualTo(Samplers.alwaysSample()); + assertThat(options.getParentLinks()).isEqualTo(singleParentList); + assertThat(options.getRecordEvents()).isTrue(); + } + + @Test + public void startSpanOptions_EqualsAndHashCode() { + EqualsTester tester = new EqualsTester(); + StartSpanOptions optionsWithAlwaysSampler1 = new StartSpanOptions(); + optionsWithAlwaysSampler1.setSampler(Samplers.alwaysSample()); + StartSpanOptions optionsWithAlwaysSampler2 = new StartSpanOptions(); + optionsWithAlwaysSampler2.setSampler(Samplers.alwaysSample()); + tester.addEqualityGroup(optionsWithAlwaysSampler1, optionsWithAlwaysSampler2); + StartSpanOptions optionsWithNeverSampler = new StartSpanOptions(); + optionsWithNeverSampler.setSampler(Samplers.neverSample()); + tester.addEqualityGroup(optionsWithNeverSampler); + tester.addEqualityGroup(new StartSpanOptions()); + tester.testEquals(); + } +} diff --git a/api/src/test/java/io/opencensus/trace/StatusTest.java b/api/src/test/java/io/opencensus/trace/StatusTest.java new file mode 100644 index 00000000..c66a3428 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/StatusTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.testing.EqualsTester; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link Status}. */ +@RunWith(JUnit4.class) +public class StatusTest { + @Test + public void status_Ok() { + assertThat(Status.OK.getCanonicalCode()).isEqualTo(Status.CanonicalCode.OK); + assertThat(Status.OK.getDescription()).isNull(); + assertThat(Status.OK.isOk()).isTrue(); + } + + @Test + public void createStatus_WithDescription() { + Status status = Status.UNKNOWN.withDescription("This is an error."); + assertThat(status.getCanonicalCode()).isEqualTo(Status.CanonicalCode.UNKNOWN); + assertThat(status.getDescription()).isEqualTo("This is an error."); + assertThat(status.isOk()).isFalse(); + } + + @Test + public void status_EqualsAndHashCode() { + EqualsTester tester = new EqualsTester(); + tester.addEqualityGroup(Status.OK, Status.OK.withDescription(null)); + tester.addEqualityGroup( + Status.CANCELLED.withDescription("ThisIsAnError"), + Status.CANCELLED.withDescription("ThisIsAnError")); + tester.addEqualityGroup(Status.UNKNOWN.withDescription("This is an error.")); + tester.testEquals(); + } +} diff --git a/api/src/test/java/io/opencensus/trace/TraceComponentTest.java b/api/src/test/java/io/opencensus/trace/TraceComponentTest.java new file mode 100644 index 00000000..106cb965 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/TraceComponentTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.internal.ZeroTimeClock; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link TraceComponent}. */ +@RunWith(JUnit4.class) +public class TraceComponentTest { + @Test + public void defaultTracer() { + assertThat(TraceComponent.getNoopTraceComponent().getTracer()).isSameAs(Tracer.getNoopTracer()); + } + + @Test + public void defaultBinaryPropagationHandler() { + assertThat(TraceComponent.getNoopTraceComponent().getBinaryPropagationHandler()) + .isSameAs(BinaryPropagationHandler.getNoopBinaryPropagationHandler()); + } + + @Test + public void defaultClock() { + assertThat(TraceComponent.getNoopTraceComponent().getClock()).isInstanceOf(ZeroTimeClock.class); + } + + @Test + public void defaultTraceExporter() { + assertThat(TraceComponent.getNoopTraceComponent().getTraceExporter()) + .isSameAs(TraceExporter.getNoopTraceExporter()); + } + + @Test + public void defaultTraceConfig() { + assertThat(TraceComponent.getNoopTraceComponent().getTraceConfig()) + .isSameAs(TraceConfig.getNoopTraceConfig()); + } +} diff --git a/api/src/test/java/io/opencensus/trace/TraceExporterTest.java b/api/src/test/java/io/opencensus/trace/TraceExporterTest.java new file mode 100644 index 00000000..3837a1ad --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/TraceExporterTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; + +import io.opencensus.trace.TraceExporter.LoggingServiceHandler; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link TraceExporter}. */ +@RunWith(JUnit4.class) +public class TraceExporterTest { + @Mock private TraceExporter traceExporter; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void registerUnregisterLoggingService() { + LoggingServiceHandler.registerService(traceExporter); + verify(traceExporter) + .registerServiceHandler( + eq("io.opencensus.trace.LoggingServiceHandler"), any(LoggingServiceHandler.class)); + LoggingServiceHandler.unregisterService(traceExporter); + verify(traceExporter).unregisterServiceHandler(eq("io.opencensus.trace.LoggingServiceHandler")); + } +} diff --git a/api/src/test/java/io/opencensus/trace/TraceIdTest.java b/api/src/test/java/io/opencensus/trace/TraceIdTest.java new file mode 100644 index 00000000..cd1b7c24 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/TraceIdTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.testing.EqualsTester; +import java.util.Arrays; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link TraceId}. */ +@RunWith(JUnit4.class) +public class TraceIdTest { + private static final byte[] firstBytes = + new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'a'}; + private static final byte[] secondBytes = + new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'A'}; + private static final TraceId first = TraceId.fromBytes(firstBytes); + private static final TraceId second = TraceId.fromBytes(secondBytes); + + @Test + public void invalidTraceId() { + assertThat(TraceId.INVALID.getBytes()).isEqualTo(new byte[16]); + } + + @Test + public void isValid() { + assertThat(TraceId.INVALID.isValid()).isFalse(); + assertThat(first.isValid()).isTrue(); + assertThat(second.isValid()).isTrue(); + } + + @Test + public void getBytes() { + assertThat(first.getBytes()).isEqualTo(firstBytes); + assertThat(second.getBytes()).isEqualTo(secondBytes); + } + + @Test + public void traceId_CompareTo() { + assertThat(first.compareTo(second)).isGreaterThan(0); + assertThat(second.compareTo(first)).isLessThan(0); + assertThat(first.compareTo(TraceId.fromBytes(firstBytes))).isEqualTo(0); + } + + @Test + public void traceId_EqualsAndHashCode() { + EqualsTester tester = new EqualsTester(); + tester.addEqualityGroup(TraceId.INVALID, TraceId.INVALID); + tester.addEqualityGroup(first, TraceId.fromBytes(Arrays.copyOf(firstBytes, firstBytes.length))); + tester.addEqualityGroup( + second, TraceId.fromBytes(Arrays.copyOf(secondBytes, secondBytes.length))); + tester.testEquals(); + } + + @Test + public void traceId_ToString() { + assertThat(TraceId.INVALID.toString()).contains("00000000000000000000000000000000"); + assertThat(first.toString()).contains("00000000000000000000000000000061"); + assertThat(second.toString()).contains("00000000000000000000000000000041"); + } +} diff --git a/api/src/test/java/io/opencensus/trace/TraceOptionsTest.java b/api/src/test/java/io/opencensus/trace/TraceOptionsTest.java new file mode 100644 index 00000000..fb240646 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/TraceOptionsTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.testing.EqualsTester; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link TraceOptions}. */ +@RunWith(JUnit4.class) +public class TraceOptionsTest { + private static final byte[] firstBytes = {(byte) 0xff}; + private static final byte[] secondBytes = {1}; + private static final byte[] thirdBytes = {6}; + + @Test + public void getOptions() { + assertThat(TraceOptions.DEFAULT.getOptions()).isEqualTo(0); + assertThat(TraceOptions.builder().setIsSampled().build().getOptions()).isEqualTo(1); + assertThat(TraceOptions.fromBytes(firstBytes).getOptions()).isEqualTo(-1); + assertThat(TraceOptions.fromBytes(secondBytes).getOptions()).isEqualTo(1); + assertThat(TraceOptions.fromBytes(thirdBytes).getOptions()).isEqualTo(6); + } + + @Test + public void isSampled() { + assertThat(TraceOptions.DEFAULT.isSampled()).isFalse(); + assertThat(TraceOptions.builder().setIsSampled().build().isSampled()).isTrue(); + } + + @Test + public void toFromBytes() { + assertThat(TraceOptions.fromBytes(firstBytes).getBytes()).isEqualTo(firstBytes); + assertThat(TraceOptions.fromBytes(secondBytes).getBytes()).isEqualTo(secondBytes); + assertThat(TraceOptions.fromBytes(thirdBytes).getBytes()).isEqualTo(thirdBytes); + } + + @Test + public void builder_FromOptions() { + assertThat( + TraceOptions.builder(TraceOptions.fromBytes(thirdBytes)) + .setIsSampled() + .build() + .getOptions()) + .isEqualTo(6 | 1); + } + + @Test + public void traceOptions_EqualsAndHashCode() { + EqualsTester tester = new EqualsTester(); + tester.addEqualityGroup(TraceOptions.DEFAULT); + tester.addEqualityGroup( + TraceOptions.fromBytes(secondBytes), TraceOptions.builder().setIsSampled().build()); + tester.addEqualityGroup(TraceOptions.fromBytes(firstBytes)); + tester.testEquals(); + } + + @Test + public void traceOptions_ToString() { + assertThat(TraceOptions.DEFAULT.toString()).contains("sampled=false"); + assertThat(TraceOptions.builder().setIsSampled().build().toString()).contains("sampled=true"); + } +} diff --git a/api/src/test/java/io/opencensus/trace/TraceParamsTest.java b/api/src/test/java/io/opencensus/trace/TraceParamsTest.java new file mode 100644 index 00000000..60f40deb --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/TraceParamsTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.trace.TraceConfig.TraceParams; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link TraceParams}. */ +@RunWith(JUnit4.class) +public class TraceParamsTest { + @Test + public void defaultTraceParams() { + assertThat(TraceParams.DEFAULT.getSampler()).isEqualTo(Samplers.probabilitySampler(1e-4)); + assertThat(TraceParams.DEFAULT.getMaxNumberOfAttributes()).isEqualTo(32); + assertThat(TraceParams.DEFAULT.getMaxNumberOfAnnotations()).isEqualTo(32); + assertThat(TraceParams.DEFAULT.getMaxNumberOfNetworkEvents()).isEqualTo(128); + assertThat(TraceParams.DEFAULT.getMaxNumberOfLinks()).isEqualTo(128); + } + + @Test(expected = NullPointerException.class) + public void updateTraceParams_NullSampler() { + TraceParams.DEFAULT.toBuilder().setSampler(null).build(); + } + + @Test(expected = IllegalArgumentException.class) + public void updateTraceParams_NonPositiveMaxNumberOfAttributes() { + TraceParams.DEFAULT.toBuilder().setMaxNumberOfAttributes(0).build(); + } + + @Test(expected = IllegalArgumentException.class) + public void updateTraceParams_NonPositiveMaxNumberOfAnnotations() { + TraceParams.DEFAULT.toBuilder().setMaxNumberOfAnnotations(0).build(); + } + + @Test(expected = IllegalArgumentException.class) + public void updateTraceParams_NonPositiveMaxNumberOfNetworkEvents() { + TraceParams.DEFAULT.toBuilder().setMaxNumberOfNetworkEvents(0).build(); + } + + @Test(expected = IllegalArgumentException.class) + public void updateTraceParams_NonPositiveMaxNumberOfLinks() { + TraceParams.DEFAULT.toBuilder().setMaxNumberOfLinks(0).build(); + } + + @Test + public void updateTraceParams_All() { + TraceParams traceParams = + TraceParams.DEFAULT + .toBuilder() + .setSampler(Samplers.alwaysSample()) + .setMaxNumberOfAttributes(8) + .setMaxNumberOfAnnotations(9) + .setMaxNumberOfNetworkEvents(10) + .setMaxNumberOfLinks(11) + .build(); + assertThat(traceParams.getSampler()).isEqualTo(Samplers.alwaysSample()); + assertThat(traceParams.getMaxNumberOfAttributes()).isEqualTo(8); + assertThat(traceParams.getMaxNumberOfAnnotations()).isEqualTo(9); + assertThat(traceParams.getMaxNumberOfNetworkEvents()).isEqualTo(10); + assertThat(traceParams.getMaxNumberOfLinks()).isEqualTo(11); + } +} diff --git a/api/src/test/java/io/opencensus/trace/TracerTest.java b/api/src/test/java/io/opencensus/trace/TracerTest.java new file mode 100644 index 00000000..cc46956c --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/TracerTest.java @@ -0,0 +1,210 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.grpc.Context; +import io.opencensus.common.NonThrowingCloseable; +import java.util.Random; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link Tracer}. */ +@RunWith(JUnit4.class) +public class TracerTest { + private static final Tracer tracer = Tracing.getTracer(); + private static final String SPAN_NAME = "MySpanName"; + @Rule public ExpectedException thrown = ExpectedException.none(); + @Mock private SpanFactory spanFactory; + @Mock private Span span; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void defaultGetCurrentSpan() { + assertThat(tracer.getCurrentSpan()).isEqualTo(BlankSpan.INSTANCE); + } + + @Test(expected = NullPointerException.class) + public void withSpan_NullSpan() { + tracer.withSpan(null); + } + + @Test + public void getCurrentSpan_WithSpan() { + assertThat(tracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE); + NonThrowingCloseable ws = tracer.withSpan(span); + try { + assertThat(tracer.getCurrentSpan()).isSameAs(span); + } finally { + ws.close(); + } + assertThat(tracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE); + } + + @Test + public void propagationViaRunnable() { + Runnable runnable = null; + NonThrowingCloseable ws = tracer.withSpan(span); + try { + assertThat(tracer.getCurrentSpan()).isSameAs(span); + runnable = + Context.current() + .wrap( + new Runnable() { + @Override + public void run() { + assertThat(tracer.getCurrentSpan()).isSameAs(span); + } + }); + } finally { + ws.close(); + } + assertThat(tracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE); + // When we run the runnable we will have the span in the current Context. + runnable.run(); + assertThat(tracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE); + } + + @Test(expected = NullPointerException.class) + public void spanBuilderWithName_NullName() { + assertThat(tracer.spanBuilder(null).startSpan()).isSameAs(BlankSpan.INSTANCE); + } + + @Test + public void defaultSpanBuilderWithName() { + assertThat(tracer.spanBuilder(SPAN_NAME).startSpan()).isSameAs(BlankSpan.INSTANCE); + } + + @Test(expected = NullPointerException.class) + public void spanBuilderWithParentAndName_NullName() { + assertThat(tracer.spanBuilder(null, null).startSpan()).isSameAs(BlankSpan.INSTANCE); + } + + @Test + public void defaultSpanBuilderWithParentAndName() { + assertThat(tracer.spanBuilder(null, SPAN_NAME).startSpan()).isSameAs(BlankSpan.INSTANCE); + } + + @Test(expected = NullPointerException.class) + public void spanBuilderWithRemoteParent_NullName() { + assertThat(tracer.spanBuilderWithRemoteParent(null, null).startSpan()) + .isSameAs(BlankSpan.INSTANCE); + } + + @Test + public void defaultSpanBuilderWitRemoteParent() { + assertThat(tracer.spanBuilderWithRemoteParent(null, SPAN_NAME).startSpan()) + .isSameAs(BlankSpan.INSTANCE); + } + + @Test + public void startScopedSpanRoot() { + Tracer mockTracer = new MockTracer(spanFactory); + when(spanFactory.startSpan(isNull(Span.class), same(SPAN_NAME), eq(new StartSpanOptions()))) + .thenReturn(span); + NonThrowingCloseable ss = mockTracer.spanBuilder(SPAN_NAME).becomeRoot().startScopedSpan(); + try { + assertThat(tracer.getCurrentSpan()).isSameAs(span); + } finally { + ss.close(); + } + verify(span).end(same(EndSpanOptions.DEFAULT)); + } + + @Test + public void startScopedSpanChild() { + Tracer mockTracer = new MockTracer(spanFactory); + NonThrowingCloseable ws = mockTracer.withSpan(BlankSpan.INSTANCE); + try { + assertThat(tracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE); + when(spanFactory.startSpan( + same(BlankSpan.INSTANCE), same(SPAN_NAME), eq(new StartSpanOptions()))) + .thenReturn(span); + NonThrowingCloseable ss = mockTracer.spanBuilder(SPAN_NAME).startScopedSpan(); + try { + assertThat(tracer.getCurrentSpan()).isSameAs(span); + } finally { + ss.close(); + } + assertThat(tracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE); + } finally { + ws.close(); + } + verify(span).end(same(EndSpanOptions.DEFAULT)); + } + + @Test + public void startRootSpan() { + Tracer mockTracer = new MockTracer(spanFactory); + when(spanFactory.startSpan(isNull(Span.class), same(SPAN_NAME), eq(new StartSpanOptions()))) + .thenReturn(span); + Span rootSpan = mockTracer.spanBuilder(BlankSpan.INSTANCE, SPAN_NAME).becomeRoot().startSpan(); + assertThat(rootSpan).isEqualTo(span); + rootSpan.end(); + verify(span).end(same(EndSpanOptions.DEFAULT)); + } + + @Test + public void startChildSpan() { + Tracer mockTracer = new MockTracer(spanFactory); + when(spanFactory.startSpan( + same(BlankSpan.INSTANCE), same(SPAN_NAME), eq(new StartSpanOptions()))) + .thenReturn(span); + Span childSpan = mockTracer.spanBuilder(BlankSpan.INSTANCE, SPAN_NAME).startSpan(); + assertThat(childSpan).isEqualTo(span); + childSpan.end(); + verify(span).end(same(EndSpanOptions.DEFAULT)); + } + + @Test + public void startSpanWitRemoteParent() { + Random random = new Random(1234); + Tracer mockTracer = new MockTracer(spanFactory); + SpanContext spanContext = + SpanContext.create( + TraceId.generateRandomId(random), + SpanId.generateRandomId(random), + TraceOptions.DEFAULT); + when(spanFactory.startSpanWithRemoteParent( + same(spanContext), same(SPAN_NAME), eq(new StartSpanOptions()))) + .thenReturn(span); + Span remoteChildSpan = + mockTracer.spanBuilderWithRemoteParent(spanContext, SPAN_NAME).startSpan(); + assertThat(remoteChildSpan).isEqualTo(span); + remoteChildSpan.end(); + verify(span).end(same(EndSpanOptions.DEFAULT)); + } + + private static final class MockTracer extends Tracer { + private MockTracer(SpanFactory spanFactory) { + super(spanFactory); + } + } +} diff --git a/api/src/test/java/io/opencensus/trace/TracingTest.java b/api/src/test/java/io/opencensus/trace/TracingTest.java new file mode 100644 index 00000000..b7c9be25 --- /dev/null +++ b/api/src/test/java/io/opencensus/trace/TracingTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2016, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link Tracing}. */ +@RunWith(JUnit4.class) +public class TracingTest { + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + public void loadTraceService_UsesProvidedClassLoader() { + final RuntimeException toThrow = new RuntimeException("UseClassLoader"); + thrown.expect(RuntimeException.class); + thrown.expectMessage("UseClassLoader"); + Tracing.loadTraceComponent( + new ClassLoader() { + @Override + public Class<?> loadClass(String name) { + throw toThrow; + } + }); + } + + @Test + public void loadSpanFactory_IgnoresMissingClasses() { + assertThat( + Tracing.loadTraceComponent( + new ClassLoader() { + @Override + public Class<?> loadClass(String name) throws ClassNotFoundException { + throw new ClassNotFoundException(); + } + }) + .getClass() + .getName()) + .isEqualTo("io.opencensus.trace.TraceComponent$NoopTraceComponent"); + } + + @Test + public void defaultTracer() { + assertThat(Tracing.getTracer()).isSameAs(Tracer.getNoopTracer()); + } + + @Test + public void defaultBinaryPropagationHandler() { + assertThat(Tracing.getBinaryPropagationHandler()) + .isSameAs(BinaryPropagationHandler.getNoopBinaryPropagationHandler()); + } + + @Test + public void defaultTraceExporter() { + assertThat(Tracing.getTraceExporter()).isSameAs(TraceExporter.getNoopTraceExporter()); + } + + @Test + public void defaultTraceConfig() { + assertThat(Tracing.getTraceConfig()).isSameAs(TraceConfig.getNoopTraceConfig()); + } +} |
