aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorBogdan Drutu <bdrutu@google.com>2017-03-15 14:48:11 -0700
committerGitHub <noreply@github.com>2017-03-15 14:48:11 -0700
commit8c1d4d23d5488b0fa80117f9355b2f9a3f3082da (patch)
tree37813c18d9c8f87c5c7858949898220c5f447444 /core
parent544d349f4c3bdbf7fc86444c3c36fba5801710b3 (diff)
downloadplatform_external_opencensus-java-8c1d4d23d5488b0fa80117f9355b2f9a3f3082da.tar.gz
platform_external_opencensus-java-8c1d4d23d5488b0fa80117f9355b2f9a3f3082da.tar.bz2
platform_external_opencensus-java-8c1d4d23d5488b0fa80117f9355b2f9a3f3082da.zip
Add an util class to be used for HTTP propagation of the SpanContext. (#139)
* Add an util class to be used for HTTP propagation of the SpanContext. * First round of comments. Upper-case characters for encoding. Use offset instead of position. Second round of comments.
Diffstat (limited to 'core')
-rw-r--r--core/src/main/java/com/google/instrumentation/trace/HttpPropagationUtil.java140
-rw-r--r--core/src/main/java/com/google/instrumentation/trace/SpanId.java90
-rw-r--r--core/src/main/java/com/google/instrumentation/trace/TraceId.java93
-rw-r--r--core/src/main/java/com/google/instrumentation/trace/TraceOptions.java97
-rw-r--r--core/src/test/java/com/google/instrumentation/trace/HttpPropagationUtilTest.java165
5 files changed, 506 insertions, 79 deletions
diff --git a/core/src/main/java/com/google/instrumentation/trace/HttpPropagationUtil.java b/core/src/main/java/com/google/instrumentation/trace/HttpPropagationUtil.java
new file mode 100644
index 00000000..cc676e0a
--- /dev/null
+++ b/core/src/main/java/com/google/instrumentation/trace/HttpPropagationUtil.java
@@ -0,0 +1,140 @@
+/*
+ * 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 com.google.instrumentation.trace;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.io.BaseEncoding;
+
+/**
+ * This is a helper class for {@link SpanContext} propagation on the wire when using the HTTP
+ * transport protocol.
+ *
+ * <p>HTTP header format:
+ *
+ * <ul>
+ * <li>Header name: Trace-Context
+ * <li>Header value: base16(&lt;version_id&gt;)&lt;version_format&gt;
+ * <li>version_id: 1-byte representing the version id.
+ * <li>For version_id = 0:
+ * <ul>
+ * <li>version_format: base16(&lt;trace-id&gt;&lt;span-id&gt;&lt;trace-options&gt;)
+ * <li>trace-id: 16-byte array representing the trace_id.
+ * <li>span-id: 8-byte array representing the span_id.
+ * <li>trace-options: 4-byte array representing the trace_options.
+ * <li>Valid value example:
+ * <ul>
+ * <li>"00404142434445464748494A4B4C4D4E4F616263646566676800000001"
+ * <li>version_id = 0;
+ * <li>trace_id = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}
+ * <li>span_id = {97, 98, 99, 100, 101, 102, 103, 104};
+ * <li>trace_options = {0, 0, 0, 1};
+ * </ul>
+ * </ul>
+ *
+ * </ul>
+ *
+ * <p>All characters in the header value must be upper case and US-ASCII encoded.
+ *
+ * <p>It is strongly encouraged to use this format when using HTTP as a RPC transport.
+ *
+ * <p>Example of usage on the client:
+ *
+ * <pre>{@code
+ * private static final Tracer tracer = Tracer.getTracer();
+ * void onSendRequest() {
+ * try (NonThrowingCloseable ss = tracer.spanBuilder("Sent.MyRequest")) {
+ * String headerName = HttpPropagationUtil.HTTP_HEADER_NAME;
+ * String headerValue = HttpPropagationUtil.toHttpHeaderValue(span.context());
+ * headers.add(headerName, headerValue);
+ * // Send the HTTP request and wait for the response.
+ * }
+ * }
+ * }</pre>
+ *
+ * <p>Example of usage on the server:
+ *
+ * <pre>{@code
+ * private static final Tracer tracer = Tracer.getTracer();
+ * void onRequestReceived() {
+ * String headerName = HttpPropagationUtil.HTTP_HEADER_NAME;
+ * SpanContext spanContext = HttpPropagationUtil.fromHttpHeaderValue(headers.find(headerName));
+ * try (NonThrowingCloseable ss =
+ * tracer.spanBuilderWithRemoteParent(spanContext, "Recv.MyRequest").startScopedSpan() {
+ * // Handle request and send response back.
+ * }
+ * }
+ * }</pre>
+ */
+public final class HttpPropagationUtil {
+ private static final byte VERSION_ID = 0;
+ // The version_id size in bytes.
+ private static final byte VERSION_ID_SIZE = 1;
+ private static final int BINARY_VERSION_TRACE_ID_OFFSET = VERSION_ID_SIZE;
+ private static final int BINARY_VERSION_SPAN_ID_OFFSET = VERSION_ID_SIZE + TraceId.SIZE;
+ private static final int BINARY_VERSION_TRACE_OPTIONS_OFFSET =
+ VERSION_ID_SIZE + TraceId.SIZE + SpanId.SIZE;
+ private static final int BINARY_VERSION_FORMAT_LENGTH =
+ VERSION_ID_SIZE + TraceId.SIZE + SpanId.SIZE + TraceOptions.SIZE;
+ private static final int HEX_VERSION_FORMAT_LENGTH = 2 * BINARY_VERSION_FORMAT_LENGTH;
+
+ /** The header name that must be used in the HTTP request for the tracing context. */
+ public static final String HTTP_HEADER_NAME = "Trace-Context";
+
+ // Disallow instances of this class.
+ private HttpPropagationUtil() {}
+
+
+ /**
+ * Serializes a {@link SpanContext} using the HTTP standard format.
+ *
+ * @param spanContext the {@code SpanContext} to serialize.
+ * @return the serialized US-ASCII encoded HTTP header value.
+ * @throws NullPointerException if the {@code spanContext} is null.
+ */
+ public static String toHttpHeaderValue(SpanContext spanContext) {
+ checkNotNull(spanContext, "spanContext");
+ byte[] bytes = new byte[BINARY_VERSION_FORMAT_LENGTH];
+ bytes[0] = VERSION_ID;
+ spanContext.getTraceId().copyBytesTo(bytes, BINARY_VERSION_TRACE_ID_OFFSET);
+ spanContext.getSpanId().copyBytesTo(bytes, BINARY_VERSION_SPAN_ID_OFFSET);
+ spanContext.getTraceOptions().copyBytesTo(bytes, BINARY_VERSION_TRACE_OPTIONS_OFFSET);
+ return BaseEncoding.base16().encode(bytes);
+ }
+
+ /**
+ * Parses the {@link SpanContext} from the HTTP standard format.
+ *
+ * @param input a US-ASCII encoded buffer of characters from which the {@code SpanContext} will be
+ * parsed.
+ * @return the parsed {@code SpanContext}.
+ * @throws NullPointerException if the {@code input} is null.
+ * @throws IllegalArgumentException if the {@code input} is invalid.
+ */
+ public static SpanContext fromHttpHeaderValue(CharSequence input) {
+ checkNotNull(input, "input");
+ checkArgument(
+ input.length() == HEX_VERSION_FORMAT_LENGTH,
+ "Invalid input size: expected %s, got %s",
+ HEX_VERSION_FORMAT_LENGTH,
+ input.length());
+ byte[] bytes = BaseEncoding.base16().decode(input);
+ checkArgument(bytes[0] == VERSION_ID, "Unsupported version.");
+ return new SpanContext(
+ TraceId.fromBytes(bytes, BINARY_VERSION_TRACE_ID_OFFSET),
+ SpanId.fromBytes(bytes, BINARY_VERSION_SPAN_ID_OFFSET),
+ TraceOptions.fromBytes(bytes, BINARY_VERSION_TRACE_OPTIONS_OFFSET));
+ }
+}
diff --git a/core/src/main/java/com/google/instrumentation/trace/SpanId.java b/core/src/main/java/com/google/instrumentation/trace/SpanId.java
index 70ed6a14..c6e99a89 100644
--- a/core/src/main/java/com/google/instrumentation/trace/SpanId.java
+++ b/core/src/main/java/com/google/instrumentation/trace/SpanId.java
@@ -14,22 +14,27 @@
package com.google.instrumentation.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.
+ * 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 span id.
- private static final int SPAN_ID_SIZE = 8;
+ /** 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) {
@@ -37,22 +42,42 @@ public final class SpanId implements Comparable<SpanId> {
}
/**
- * The invalid {@code SpanId}. All bytes are 0.
+ * 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 final SpanId INVALID = new SpanId(new byte[SPAN_ID_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 given param.
+ * Returns a {@code SpanId} whose representation is copied from the {@code src} beginning at the
+ * {@code srcOffset} offset.
*
- * @param bytes the representation of the {@code SpanId}.
- * @return a {@code SpanId} whose representation is given param.
- * @throws NullPointerException if bytes is null.
- * @throws IllegalArgumentException if bytes length is not 8.
+ * @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 IllegalArgumentException if {@code srcOffset+SpanId.SIZE} is greater than {@code
+ * src.length}.
*/
- public static SpanId fromBytes(byte[] bytes) {
- checkArgument(bytes.length == SPAN_ID_SIZE, "bytes");
- byte[] bytesCopy = Arrays.copyOf(bytes, SPAN_ID_SIZE);
- return Arrays.equals(bytesCopy, INVALID.bytes) ? INVALID : new SpanId(bytes);
+ public static SpanId fromBytes(byte[] src, int srcOffset) {
+ byte[] bytes = new byte[SIZE];
+ System.arraycopy(src, srcOffset, bytes, 0, SIZE);
+ return new SpanId(bytes);
}
/**
@@ -62,7 +87,7 @@ public final class SpanId implements Comparable<SpanId> {
* @return a valid new {@code SpanId}.
*/
public static SpanId generateRandomId(Random random) {
- byte[] bytes = new byte[SPAN_ID_SIZE];
+ byte[] bytes = new byte[SIZE];
do {
random.nextBytes(bytes);
} while (Arrays.equals(bytes, INVALID.bytes));
@@ -70,14 +95,33 @@ public final class SpanId implements Comparable<SpanId> {
}
/**
- * Returns the 8-bytes array representation of the {@code SpanId}.
+ * Returns the byte representation of the {@code SpanId}.
*
- * @return the 8-bytes array representation of the {@code SpanId}.
+ * @return the byte representation of the {@code SpanId}.
*/
public byte[] getBytes() {
- return Arrays.copyOf(bytes, SPAN_ID_SIZE);
+ 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
@@ -111,15 +155,13 @@ public final class SpanId implements Comparable<SpanId> {
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
- .add(
- "spanId",
- BaseEncoding.base16().lowerCase().encode(bytes))
+ .add("spanId", BaseEncoding.base16().lowerCase().encode(bytes))
.toString();
}
@Override
public int compareTo(SpanId that) {
- for (int i = 0; i < SPAN_ID_SIZE; i++) {
+ for (int i = 0; i < SIZE; i++) {
if (bytes[i] != that.bytes[i]) {
return bytes[i] < that.bytes[i] ? -1 : 1;
}
diff --git a/core/src/main/java/com/google/instrumentation/trace/TraceId.java b/core/src/main/java/com/google/instrumentation/trace/TraceId.java
index 20d2e108..389007ee 100644
--- a/core/src/main/java/com/google/instrumentation/trace/TraceId.java
+++ b/core/src/main/java/com/google/instrumentation/trace/TraceId.java
@@ -14,45 +14,70 @@
package com.google.instrumentation.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.
+ * 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 trace id.
- private static final int TRACE_ID_SIZE = 16;
- private final byte[] bytes;
+ /** 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[TRACE_ID_SIZE]);
+ /** 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} whose representation is given param.
+ * Returns a {@code TraceId} built from a byte representation.
*
- * @param bytes the representation of the {@code TraceId}.
- * @return a {@code TraceId} whose representation is given param.
- * @throws NullPointerException if bytes is null.
- * @throws IllegalArgumentException if bytes length is not 16.
+ * <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[] bytes) {
- checkArgument(bytes.length == TRACE_ID_SIZE, "bytes");
- byte[] bytesCopy = Arrays.copyOf(bytes, TRACE_ID_SIZE);
- return Arrays.equals(bytesCopy, INVALID.bytes) ? INVALID : new TraceId(bytes);
+ 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 IllegalArgumentException 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);
}
/**
@@ -62,7 +87,7 @@ public final class TraceId implements Comparable<TraceId> {
* @return a new valid {@code TraceId}.
*/
public static TraceId generateRandomId(Random random) {
- byte[] bytes = new byte[TRACE_ID_SIZE];
+ byte[] bytes = new byte[SIZE];
do {
random.nextBytes(bytes);
} while (Arrays.equals(bytes, INVALID.bytes));
@@ -75,7 +100,27 @@ public final class TraceId implements Comparable<TraceId> {
* @return the 16-bytes array representation of the {@code TraceId}.
*/
public byte[] getBytes() {
- return Arrays.copyOf(bytes, TRACE_ID_SIZE);
+ 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);
}
/**
@@ -110,15 +155,13 @@ public final class TraceId implements Comparable<TraceId> {
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
- .add(
- "traceId",
- BaseEncoding.base16().lowerCase().encode(bytes))
+ .add("traceId", BaseEncoding.base16().lowerCase().encode(bytes))
.toString();
}
@Override
public int compareTo(TraceId that) {
- for (int i = 0; i < TRACE_ID_SIZE; i++) {
+ for (int i = 0; i < SIZE; i++) {
if (bytes[i] != that.bytes[i]) {
return bytes[i] < that.bytes[i] ? -1 : 1;
}
diff --git a/core/src/main/java/com/google/instrumentation/trace/TraceOptions.java b/core/src/main/java/com/google/instrumentation/trace/TraceOptions.java
index a1198da0..03436b89 100644
--- a/core/src/main/java/com/google/instrumentation/trace/TraceOptions.java
+++ b/core/src/main/java/com/google/instrumentation/trace/TraceOptions.java
@@ -19,7 +19,6 @@ 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;
/**
@@ -36,14 +35,10 @@ public final class TraceOptions {
// Bit to represent whether trace is sampled or not.
private static final int IS_SAMPLED = 0x1;
- /**
- * The size in bytes of the {@code TraceOptions}.
- */
+ /** The size in bytes of the {@code TraceOptions}. */
public static final int SIZE = 4;
- /**
- * The default {@code TraceOptions}.
- */
+ /** 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.
@@ -57,15 +52,37 @@ public final class TraceOptions {
/**
* Returns a {@code TraceOptions} built from a byte representation.
*
- * @param bytes the representation of the {@code TraceOptions}.
- * @return a {@code TraceOptions} whose representation is given by the {@code bytes} parameter.
- * @throws NullPointerException if {@code bytes} is null.
- * @throws IllegalArgumentException if {@code bytes.length} is not 4.
+ * <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[] bytes) {
- checkNotNull(bytes);
- checkArgument(bytes.length == SIZE);
- return new TraceOptions(intFromBytes(bytes, 0));
+ 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(intFromBytes(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 IllegalArgumentException if {@code srcOffset+TraceOptions.SIZE} is greater than {@code
+ * src.length}.
+ */
+ public static TraceOptions fromBytes(byte[] src, int srcOffset) {
+ return new TraceOptions(intFromBytes(src, srcOffset));
}
/**
@@ -80,6 +97,26 @@ public final class TraceOptions {
}
/**
+ * 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) {
+ intToBytes(options, dest, destOffset);
+ }
+
+ /**
* Returns a new {@link Builder} with default options.
*
* @return a new {@code Builder} with default options.
@@ -132,9 +169,7 @@ public final class TraceOptions {
return MoreObjects.toStringHelper(this).add("sampled", isSampled()).toString();
}
- /**
- * Builder class for {@link TraceOptions}.
- */
+ /** Builder class for {@link TraceOptions}. */
public static final class Builder {
private int options;
@@ -172,19 +207,21 @@ public final class TraceOptions {
return (this.options & mask) != 0;
}
- // Returns the int value whose big-endian representation is stored in the first 4 bytes of src.
- private static int intFromBytes(byte[] src, int srcPos) {
- return src[srcPos] << (3 * Byte.SIZE)
- | (src[srcPos + 1] & BYTE_MASK) << (2 * Byte.SIZE)
- | (src[srcPos + 2] & BYTE_MASK) << Byte.SIZE
- | (src[srcPos + 3] & BYTE_MASK);
+ // Returns the int value whose big-endian representation is stored in the first 4 bytes of src
+ // starting from the given offset.
+ private static int intFromBytes(byte[] src, int srcOffset) {
+ return src[srcOffset] << (3 * Byte.SIZE)
+ | (src[srcOffset + 1] & BYTE_MASK) << (2 * Byte.SIZE)
+ | (src[srcOffset + 2] & BYTE_MASK) << Byte.SIZE
+ | (src[srcOffset + 3] & BYTE_MASK);
}
- // Appends the big-endian representation of value as a 4-element byte array to the destination.
- private static void intToBytes(int value, byte[] dest, int destPos) {
- dest[destPos] = (byte) (value >> (3 * Byte.SIZE));
- dest[destPos + 1] = (byte) (value >> (2 * Byte.SIZE));
- dest[destPos + 2] = (byte) (value >> Byte.SIZE);
- dest[destPos + 3] = (byte) value;
+ // Appends the big-endian representation of value as a 4-element byte array to the destination
+ // starting at the given offset.
+ private static void intToBytes(int value, byte[] dest, int destOffset) {
+ dest[destOffset] = (byte) (value >> (3 * Byte.SIZE));
+ dest[destOffset + 1] = (byte) (value >> (2 * Byte.SIZE));
+ dest[destOffset + 2] = (byte) (value >> Byte.SIZE);
+ dest[destOffset + 3] = (byte) value;
}
}
diff --git a/core/src/test/java/com/google/instrumentation/trace/HttpPropagationUtilTest.java b/core/src/test/java/com/google/instrumentation/trace/HttpPropagationUtilTest.java
new file mode 100644
index 00000000..508119a0
--- /dev/null
+++ b/core/src/test/java/com/google/instrumentation/trace/HttpPropagationUtilTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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 com.google.instrumentation.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link HttpPropagationUtil}. */
+@RunWith(JUnit4.class)
+public class HttpPropagationUtilTest {
+ private static final byte[] traceIdBytes =
+ new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'a'};
+ private static final TraceId traceId = TraceId.fromBytes(traceIdBytes);
+ private static final byte[] spanIdBytes = new byte[] {(byte) 0xFF, 0, 0, 0, 0, 0, 0, 0};
+ private static final SpanId spanId = SpanId.fromBytes(spanIdBytes);
+ private static final byte[] traceOptionsBytes = new byte[] {(byte) 0x12, (byte) 0x34, 0, 1};
+ private static final TraceOptions traceOptions = TraceOptions.fromBytes(traceOptionsBytes);
+
+ private static void testSpanContextConversion(SpanContext tc) {
+ SpanContext propagatedTc =
+ HttpPropagationUtil.fromHttpHeaderValue(HttpPropagationUtil.toHttpHeaderValue(tc));
+ assertWithMessage("Propagated Context is not null.").that(propagatedTc).isNotNull();
+ assertWithMessage("Propagated Context is valid.")
+ .that(propagatedTc.isValid())
+ .isEqualTo(tc.isValid());
+ assertWithMessage("Trace ids match.")
+ .that(propagatedTc.getTraceId())
+ .isEqualTo(tc.getTraceId());
+ assertWithMessage("Span ids match.").that(propagatedTc.getSpanId()).isEqualTo(tc.getSpanId());
+ assertWithMessage("TraceOptions should match.")
+ .that(propagatedTc.getTraceOptions())
+ .isEqualTo(tc.getTraceOptions());
+ }
+
+ @Test
+ public void getHttpHeaderName() {
+ assertThat(HttpPropagationUtil.HTTP_HEADER_NAME).isEqualTo("Trace-Context");
+ }
+
+ @Test
+ public void propagate_SpanContextTracingEnabled() {
+ testSpanContextConversion(
+ new SpanContext(traceId, spanId, TraceOptions.builder().setIsSampled().build()));
+ }
+
+ @Test
+ public void propagate_SpanContextNoTracing() {
+ testSpanContextConversion(new SpanContext(traceId, spanId, TraceOptions.DEFAULT));
+ }
+
+ @Test
+ public void format_SpanContext() {
+ assertThat(
+ HttpPropagationUtil.toHttpHeaderValue(new SpanContext(traceId, spanId, traceOptions)))
+ .isEqualTo("0000000000000000000000000000000061FF0000000000000012340001");
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void toHttpHeaderValue_NullSpanContext() {
+ HttpPropagationUtil.toHttpHeaderValue(null);
+ }
+
+ @Test
+ public void toHttpHeaderValue_InvalidSpanContext() {
+ assertThat(HttpPropagationUtil.toHttpHeaderValue(SpanContext.INVALID)).isEqualTo
+ ("0000000000000000000000000000000000000000000000000000000000");
+ }
+
+ @Test
+ public void parseHeaderValue_ValidValues() {
+ assertThat(
+ HttpPropagationUtil.fromHttpHeaderValue(
+ "0000000000000000000000000000000061FF0000000000000012340001"))
+ .isEqualTo(new SpanContext(traceId, spanId, traceOptions));
+
+ assertThat(
+ HttpPropagationUtil.fromHttpHeaderValue(
+ "0000000000000000000000000000000000FF0000000000000012340001"))
+ .isEqualTo(new SpanContext(TraceId.INVALID, spanId, traceOptions));
+
+ assertThat(
+ HttpPropagationUtil.fromHttpHeaderValue(
+ "0000000000000000000000000000000061000000000000000012340001"))
+ .isEqualTo(new SpanContext(traceId, SpanId.INVALID, traceOptions));
+
+ assertThat(
+ HttpPropagationUtil.fromHttpHeaderValue(
+ "0000000000000000000000000000000061FF0000000000000000000000"))
+ .isEqualTo(new SpanContext(traceId, spanId, TraceOptions.DEFAULT));
+ }
+
+ @Test
+ public void parseHeaderValue_ExampleValue() {
+ assertThat(
+ HttpPropagationUtil.fromHttpHeaderValue(
+ "00404142434445464748494A4B4C4D4E4F616263646566676800000001"))
+ .isEqualTo(
+ new SpanContext(
+ TraceId.fromBytes(
+ new byte[] {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}),
+ SpanId.fromBytes(new byte[] {97, 98, 99, 100, 101, 102, 103, 104}),
+ TraceOptions.fromBytes(new byte[] {0, 0, 0, 1})));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void parseHeaderValue_NullInput() {
+ HttpPropagationUtil.fromHttpHeaderValue(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void parseHeaderValue_EmptyInput() {
+ HttpPropagationUtil.fromHttpHeaderValue("");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void parseHeaderValue_InvalidVersionId() {
+ HttpPropagationUtil.fromHttpHeaderValue(
+ "0100000000000000000000000000000061FF0000000000000000000001");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void parseHeaderValue_LongerVersionFormat() {
+ HttpPropagationUtil.fromHttpHeaderValue(
+ "0000000000000000000000000000000061FF000000000000000000000100");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void parseHeaderValue_ShorterVersionFormat() {
+ HttpPropagationUtil.fromHttpHeaderValue(
+ "0000000000000000000000000000000000ff00000000000000000001");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void parseHeaderValue_InvalidCharactersInTraceId() {
+ HttpPropagationUtil.fromHttpHeaderValue(
+ "00000000000000007b0000SPAN000001c8ff0000000000000000000001");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void parseHeaderValue_InvalidCharactersInSpanId() {
+ HttpPropagationUtil.fromHttpHeaderValue(
+ "0000000000000000000000000000000061000000ROOT00031500000001");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void parseHeaderValue_InvalidCharactersInTraceOptions() {
+ HttpPropagationUtil.fromHttpHeaderValue(
+ "0000000000000000000000000000000061FF0000000000000000BAR001");
+ }
+}