aboutsummaryrefslogtreecommitdiff
path: root/impl_core/src/main/java/io/opencensus/implcore/trace
diff options
context:
space:
mode:
authorJulien Desprez <jdesprez@google.com>2018-10-22 11:37:22 -0700
committerandroid-build-merger <android-build-merger@google.com>2018-10-22 11:37:22 -0700
commit13217871fefa43f6d16fbb31b04e9904996d87d5 (patch)
treeede84fcf0a9687d4907ae5f8a4788271d62e0922 /impl_core/src/main/java/io/opencensus/implcore/trace
parentcfbefd32336596ea63784607e4106dc37ce0567f (diff)
parent6fbc3cf5a1a3369fd354c1e5d9f90c86e4bce0a4 (diff)
downloadopencensus-java-13217871fefa43f6d16fbb31b04e9904996d87d5.tar.gz
Merge remote-tracking branch 'aosp/upstream-master' into merge am: dd3cabeacc
am: 6fbc3cf5a1 Change-Id: I11b0ec1cf561d2a14da78e444b1594f167787fe6
Diffstat (limited to 'impl_core/src/main/java/io/opencensus/implcore/trace')
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/NoRecordEventsSpanImpl.java85
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/RecordEventsSpanImpl.java579
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/SpanBuilderImpl.java253
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/StartEndHandlerImpl.java127
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/TraceComponentImplBase.java90
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/TracerImpl.java52
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/config/TraceConfigImpl.java43
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/export/ExportComponentImpl.java93
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/export/InProcessRunningSpanStoreImpl.java81
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/export/InProcessSampledSpanStoreImpl.java396
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/export/RunningSpanStoreImpl.java71
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/export/SampledSpanStoreImpl.java81
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/export/SpanExporterImpl.java214
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/internal/ConcurrentIntrusiveList.java181
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/internal/RandomHandler.java50
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/propagation/B3Format.java113
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/propagation/BinaryFormatImpl.java148
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/propagation/PropagationComponentImpl.java37
18 files changed, 2694 insertions, 0 deletions
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/NoRecordEventsSpanImpl.java b/impl_core/src/main/java/io/opencensus/implcore/trace/NoRecordEventsSpanImpl.java
new file mode 100644
index 00000000..8a5f8e05
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/NoRecordEventsSpanImpl.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace;
+
+import com.google.common.base.Preconditions;
+import io.opencensus.trace.Annotation;
+import io.opencensus.trace.AttributeValue;
+import io.opencensus.trace.EndSpanOptions;
+import io.opencensus.trace.Link;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.Status;
+import java.util.EnumSet;
+import java.util.Map;
+
+/** Implementation for the {@link Span} class that does not record trace events. */
+final class NoRecordEventsSpanImpl extends Span {
+
+ private static final EnumSet<Options> NOT_RECORD_EVENTS_SPAN_OPTIONS =
+ EnumSet.noneOf(Span.Options.class);
+
+ static NoRecordEventsSpanImpl create(SpanContext context) {
+ return new NoRecordEventsSpanImpl(context);
+ }
+
+ @Override
+ public void addAnnotation(String description, Map<String, AttributeValue> attributes) {
+ Preconditions.checkNotNull(description, "description");
+ Preconditions.checkNotNull(attributes, "attribute");
+ }
+
+ @Override
+ public void addAnnotation(Annotation annotation) {
+ Preconditions.checkNotNull(annotation, "annotation");
+ }
+
+ @Override
+ public void putAttribute(String key, AttributeValue value) {
+ Preconditions.checkNotNull(key, "key");
+ Preconditions.checkNotNull(value, "value");
+ }
+
+ @Override
+ public void putAttributes(Map<String, AttributeValue> attributes) {
+ Preconditions.checkNotNull(attributes, "attributes");
+ }
+
+ @Override
+ public void addMessageEvent(io.opencensus.trace.MessageEvent messageEvent) {
+ Preconditions.checkNotNull(messageEvent, "messageEvent");
+ }
+
+ @Override
+ public void addLink(Link link) {
+ Preconditions.checkNotNull(link, "link");
+ }
+
+ @Override
+ public void setStatus(Status status) {
+ Preconditions.checkNotNull(status, "status");
+ }
+
+ @Override
+ public void end(EndSpanOptions options) {
+ Preconditions.checkNotNull(options, "options");
+ }
+
+ private NoRecordEventsSpanImpl(SpanContext context) {
+ super(context, NOT_RECORD_EVENTS_SPAN_OPTIONS);
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/RecordEventsSpanImpl.java b/impl_core/src/main/java/io/opencensus/implcore/trace/RecordEventsSpanImpl.java
new file mode 100644
index 00000000..af3545bc
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/RecordEventsSpanImpl.java
@@ -0,0 +1,579 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.EvictingQueue;
+import io.opencensus.common.Clock;
+import io.opencensus.implcore.internal.CheckerFrameworkUtils;
+import io.opencensus.implcore.internal.TimestampConverter;
+import io.opencensus.implcore.trace.internal.ConcurrentIntrusiveList.Element;
+import io.opencensus.trace.Annotation;
+import io.opencensus.trace.AttributeValue;
+import io.opencensus.trace.EndSpanOptions;
+import io.opencensus.trace.Link;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.Status;
+import io.opencensus.trace.Tracer;
+import io.opencensus.trace.config.TraceParams;
+import io.opencensus.trace.export.SpanData;
+import io.opencensus.trace.export.SpanData.TimedEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.LinkedHashMap;
+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.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
+// TODO(hailongwen): remove the usage of `NetworkEvent` in the future.
+/** Implementation for the {@link Span} class that records trace events. */
+@ThreadSafe
+public final class RecordEventsSpanImpl extends Span implements Element<RecordEventsSpanImpl> {
+ private static final Logger logger = Logger.getLogger(Tracer.class.getName());
+
+ private static final EnumSet<Span.Options> RECORD_EVENTS_SPAN_OPTIONS =
+ EnumSet.of(Span.Options.RECORD_EVENTS);
+
+ // The parent SpanId of this span. Null if this is a root span.
+ @Nullable private final SpanId parentSpanId;
+ // True if the parent is on a different process.
+ @Nullable private final Boolean hasRemoteParent;
+ // Active trace params when the Span was created.
+ private final TraceParams traceParams;
+ // Handler called when the span starts and ends.
+ private final StartEndHandler startEndHandler;
+ // The displayed name of the span.
+ private final String name;
+ // The kind of the span.
+ @Nullable private final Kind kind;
+ // The clock used to get the time.
+ private final Clock clock;
+ // The time converter used to convert nano time to Timestamp. This is needed because Java has
+ // millisecond granularity for Timestamp and tracing events are recorded more often.
+ @Nullable private final TimestampConverter timestampConverter;
+ // The start time of the span.
+ private final long startNanoTime;
+ // Set of recorded attributes. DO NOT CALL any other method that changes the ordering of events.
+ @GuardedBy("this")
+ @Nullable
+ private AttributesWithCapacity attributes;
+ // List of recorded annotations.
+ @GuardedBy("this")
+ @Nullable
+ private TraceEvents<EventWithNanoTime<Annotation>> annotations;
+ // List of recorded network events.
+ @GuardedBy("this")
+ @Nullable
+ private TraceEvents<EventWithNanoTime<io.opencensus.trace.MessageEvent>> messageEvents;
+ // List of recorded links to parent and child spans.
+ @GuardedBy("this")
+ @Nullable
+ private TraceEvents<Link> links;
+ // The status of the span.
+ @GuardedBy("this")
+ @Nullable
+ private Status status;
+ // The end time of the span.
+ @GuardedBy("this")
+ private long endNanoTime;
+ // True if the span is ended.
+ @GuardedBy("this")
+ private boolean hasBeenEnded;
+
+ @GuardedBy("this")
+ private boolean sampleToLocalSpanStore;
+
+ // Pointers for the ConcurrentIntrusiveList$Element. Guarded by the ConcurrentIntrusiveList.
+ @Nullable private RecordEventsSpanImpl next = null;
+ @Nullable private RecordEventsSpanImpl prev = null;
+
+ /**
+ * Creates and starts a span with the given configuration.
+ *
+ * @param context supplies the trace_id and span_id for the newly started span.
+ * @param name the displayed name for the new span.
+ * @param parentSpanId the span_id of the parent span, or null if the new span is a root span.
+ * @param hasRemoteParent {@code true} if the parentContext is remote. {@code null} if this is a
+ * root span.
+ * @param traceParams trace parameters like sampler and probability.
+ * @param startEndHandler handler called when the span starts and ends.
+ * @param timestampConverter null if the span is a root span or the parent is not sampled. If the
+ * parent is sampled, we should use the same converter to ensure ordering between tracing
+ * events.
+ * @param clock the clock used to get the time.
+ * @return a new and started span.
+ */
+ @VisibleForTesting
+ public static RecordEventsSpanImpl startSpan(
+ SpanContext context,
+ String name,
+ @Nullable Kind kind,
+ @Nullable SpanId parentSpanId,
+ @Nullable Boolean hasRemoteParent,
+ TraceParams traceParams,
+ StartEndHandler startEndHandler,
+ @Nullable TimestampConverter timestampConverter,
+ Clock clock) {
+ RecordEventsSpanImpl span =
+ new RecordEventsSpanImpl(
+ context,
+ name,
+ kind,
+ parentSpanId,
+ hasRemoteParent,
+ traceParams,
+ startEndHandler,
+ timestampConverter,
+ clock);
+ // Call onStart here instead of calling in the constructor to make sure the span is completely
+ // initialized.
+ startEndHandler.onStart(span);
+ return span;
+ }
+
+ /**
+ * Returns the name of the {@code Span}.
+ *
+ * @return the name of the {@code Span}.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the status of the {@code Span}. If not set defaults to {@link Status#OK}.
+ *
+ * @return the status of the {@code Span}.
+ */
+ public Status getStatus() {
+ synchronized (this) {
+ return getStatusWithDefault();
+ }
+ }
+
+ /**
+ * Returns the end nano time (see {@link System#nanoTime()}). If the current {@code Span} is not
+ * ended then returns {@link Clock#nowNanos()}.
+ *
+ * @return the end nano time.
+ */
+ public long getEndNanoTime() {
+ synchronized (this) {
+ return hasBeenEnded ? endNanoTime : clock.nowNanos();
+ }
+ }
+
+ /**
+ * Returns the latency of the {@code Span} in nanos. If still active then returns now() - start
+ * time.
+ *
+ * @return the latency of the {@code Span} in nanos.
+ */
+ public long getLatencyNs() {
+ synchronized (this) {
+ return hasBeenEnded ? endNanoTime - startNanoTime : clock.nowNanos() - startNanoTime;
+ }
+ }
+
+ /**
+ * Returns if the name of this {@code Span} must be register to the {@code SampledSpanStore}.
+ *
+ * @return if the name of this {@code Span} must be register to the {@code SampledSpanStore}.
+ */
+ public boolean getSampleToLocalSpanStore() {
+ synchronized (this) {
+ checkState(hasBeenEnded, "Running span does not have the SampleToLocalSpanStore set.");
+ return sampleToLocalSpanStore;
+ }
+ }
+
+ /**
+ * Returns the kind of this {@code Span}.
+ *
+ * @return the kind of this {@code Span}.
+ */
+ @Nullable
+ public Kind getKind() {
+ return kind;
+ }
+
+ /**
+ * Returns the {@code TimestampConverter} used by this {@code Span}.
+ *
+ * @return the {@code TimestampConverter} used by this {@code Span}.
+ */
+ @Nullable
+ TimestampConverter getTimestampConverter() {
+ return timestampConverter;
+ }
+
+ /**
+ * Returns an immutable representation of all the data from this {@code Span}.
+ *
+ * @return an immutable representation of all the data from this {@code Span}.
+ * @throws IllegalStateException if the Span doesn't have RECORD_EVENTS option.
+ */
+ public SpanData toSpanData() {
+ synchronized (this) {
+ SpanData.Attributes attributesSpanData =
+ attributes == null
+ ? SpanData.Attributes.create(Collections.<String, AttributeValue>emptyMap(), 0)
+ : SpanData.Attributes.create(attributes, attributes.getNumberOfDroppedAttributes());
+ SpanData.TimedEvents<Annotation> annotationsSpanData =
+ createTimedEvents(getInitializedAnnotations(), timestampConverter);
+ SpanData.TimedEvents<io.opencensus.trace.MessageEvent> messageEventsSpanData =
+ createTimedEvents(getInitializedNetworkEvents(), timestampConverter);
+ SpanData.Links linksSpanData =
+ links == null
+ ? SpanData.Links.create(Collections.<Link>emptyList(), 0)
+ : SpanData.Links.create(
+ new ArrayList<Link>(links.events), links.getNumberOfDroppedEvents());
+ return SpanData.create(
+ getContext(),
+ parentSpanId,
+ hasRemoteParent,
+ name,
+ kind,
+ CheckerFrameworkUtils.castNonNull(timestampConverter).convertNanoTime(startNanoTime),
+ attributesSpanData,
+ annotationsSpanData,
+ messageEventsSpanData,
+ linksSpanData,
+ null, // Not supported yet.
+ hasBeenEnded ? getStatusWithDefault() : null,
+ hasBeenEnded
+ ? CheckerFrameworkUtils.castNonNull(timestampConverter).convertNanoTime(endNanoTime)
+ : null);
+ }
+ }
+
+ @Override
+ public void putAttribute(String key, AttributeValue value) {
+ Preconditions.checkNotNull(key, "key");
+ Preconditions.checkNotNull(value, "value");
+ synchronized (this) {
+ if (hasBeenEnded) {
+ logger.log(Level.FINE, "Calling putAttributes() on an ended Span.");
+ return;
+ }
+ getInitializedAttributes().putAttribute(key, value);
+ }
+ }
+
+ @Override
+ public void putAttributes(Map<String, AttributeValue> attributes) {
+ Preconditions.checkNotNull(attributes, "attributes");
+ synchronized (this) {
+ if (hasBeenEnded) {
+ logger.log(Level.FINE, "Calling putAttributes() on an ended Span.");
+ return;
+ }
+ getInitializedAttributes().putAttributes(attributes);
+ }
+ }
+
+ @Override
+ public void addAnnotation(String description, Map<String, AttributeValue> attributes) {
+ Preconditions.checkNotNull(description, "description");
+ Preconditions.checkNotNull(attributes, "attribute");
+ synchronized (this) {
+ if (hasBeenEnded) {
+ logger.log(Level.FINE, "Calling addAnnotation() on an ended Span.");
+ return;
+ }
+ getInitializedAnnotations()
+ .addEvent(
+ new EventWithNanoTime<Annotation>(
+ clock.nowNanos(),
+ Annotation.fromDescriptionAndAttributes(description, attributes)));
+ }
+ }
+
+ @Override
+ public void addAnnotation(Annotation annotation) {
+ Preconditions.checkNotNull(annotation, "annotation");
+ synchronized (this) {
+ if (hasBeenEnded) {
+ logger.log(Level.FINE, "Calling addAnnotation() on an ended Span.");
+ return;
+ }
+ getInitializedAnnotations()
+ .addEvent(new EventWithNanoTime<Annotation>(clock.nowNanos(), annotation));
+ }
+ }
+
+ @Override
+ public void addMessageEvent(io.opencensus.trace.MessageEvent messageEvent) {
+ Preconditions.checkNotNull(messageEvent, "messageEvent");
+ synchronized (this) {
+ if (hasBeenEnded) {
+ logger.log(Level.FINE, "Calling addNetworkEvent() on an ended Span.");
+ return;
+ }
+ getInitializedNetworkEvents()
+ .addEvent(
+ new EventWithNanoTime<io.opencensus.trace.MessageEvent>(
+ clock.nowNanos(), checkNotNull(messageEvent, "networkEvent")));
+ }
+ }
+
+ @Override
+ public void addLink(Link link) {
+ Preconditions.checkNotNull(link, "link");
+ synchronized (this) {
+ if (hasBeenEnded) {
+ logger.log(Level.FINE, "Calling addLink() on an ended Span.");
+ return;
+ }
+ getInitializedLinks().addEvent(link);
+ }
+ }
+
+ @Override
+ public void setStatus(Status status) {
+ Preconditions.checkNotNull(status, "status");
+ synchronized (this) {
+ if (hasBeenEnded) {
+ logger.log(Level.FINE, "Calling setStatus() on an ended Span.");
+ return;
+ }
+ this.status = status;
+ }
+ }
+
+ @Override
+ public void end(EndSpanOptions options) {
+ Preconditions.checkNotNull(options, "options");
+ synchronized (this) {
+ if (hasBeenEnded) {
+ logger.log(Level.FINE, "Calling end() on an ended Span.");
+ return;
+ }
+ if (options.getStatus() != null) {
+ status = options.getStatus();
+ }
+ sampleToLocalSpanStore = options.getSampleToLocalSpanStore();
+ endNanoTime = clock.nowNanos();
+ hasBeenEnded = true;
+ }
+ startEndHandler.onEnd(this);
+ }
+
+ @GuardedBy("this")
+ private AttributesWithCapacity getInitializedAttributes() {
+ if (attributes == null) {
+ attributes = new AttributesWithCapacity(traceParams.getMaxNumberOfAttributes());
+ }
+ return attributes;
+ }
+
+ @GuardedBy("this")
+ private TraceEvents<EventWithNanoTime<Annotation>> getInitializedAnnotations() {
+ if (annotations == null) {
+ annotations =
+ new TraceEvents<EventWithNanoTime<Annotation>>(traceParams.getMaxNumberOfAnnotations());
+ }
+ return annotations;
+ }
+
+ @GuardedBy("this")
+ private TraceEvents<EventWithNanoTime<io.opencensus.trace.MessageEvent>>
+ getInitializedNetworkEvents() {
+ if (messageEvents == null) {
+ messageEvents =
+ new TraceEvents<EventWithNanoTime<io.opencensus.trace.MessageEvent>>(
+ traceParams.getMaxNumberOfMessageEvents());
+ }
+ return messageEvents;
+ }
+
+ @GuardedBy("this")
+ private TraceEvents<Link> getInitializedLinks() {
+ if (links == null) {
+ links = new TraceEvents<Link>(traceParams.getMaxNumberOfLinks());
+ }
+ return links;
+ }
+
+ @GuardedBy("this")
+ private Status getStatusWithDefault() {
+ return status == null ? Status.OK : status;
+ }
+
+ private static <T> SpanData.TimedEvents<T> createTimedEvents(
+ TraceEvents<EventWithNanoTime<T>> events, @Nullable TimestampConverter timestampConverter) {
+ if (events == null) {
+ return SpanData.TimedEvents.create(Collections.<TimedEvent<T>>emptyList(), 0);
+ }
+ List<TimedEvent<T>> eventsList = new ArrayList<TimedEvent<T>>(events.events.size());
+ for (EventWithNanoTime<T> networkEvent : events.events) {
+ eventsList.add(
+ networkEvent.toSpanDataTimedEvent(CheckerFrameworkUtils.castNonNull(timestampConverter)));
+ }
+ return SpanData.TimedEvents.create(eventsList, events.getNumberOfDroppedEvents());
+ }
+
+ @Override
+ @Nullable
+ public RecordEventsSpanImpl getNext() {
+ return next;
+ }
+
+ @Override
+ public void setNext(@Nullable RecordEventsSpanImpl element) {
+ next = element;
+ }
+
+ @Override
+ @Nullable
+ public RecordEventsSpanImpl getPrev() {
+ return prev;
+ }
+
+ @Override
+ public void setPrev(@Nullable RecordEventsSpanImpl element) {
+ prev = element;
+ }
+
+ /**
+ * Interface to handle the start and end operations for a {@link Span} only when the {@code Span}
+ * has {@link Options#RECORD_EVENTS} option.
+ *
+ * <p>Implementation must avoid high overhead work in any of the methods because the code is
+ * executed on the critical path.
+ *
+ * <p>One instance can be called by multiple threads in the same time, so the implementation must
+ * be thread-safe.
+ */
+ public interface StartEndHandler {
+ void onStart(RecordEventsSpanImpl span);
+
+ void onEnd(RecordEventsSpanImpl span);
+ }
+
+ // A map implementation with a fixed capacity that drops events when the map gets full. Eviction
+ // is based on the access order.
+ private static final class AttributesWithCapacity extends LinkedHashMap<String, AttributeValue> {
+ private final int capacity;
+ private int totalRecordedAttributes = 0;
+ // Here because -Werror complains about this: [serial] serializable class AttributesWithCapacity
+ // has no definition of serialVersionUID. This class shouldn't be serialized.
+ private static final long serialVersionUID = 42L;
+
+ private AttributesWithCapacity(int capacity) {
+ // Capacity of the map is capacity + 1 to avoid resizing because removeEldestEntry is invoked
+ // by put and putAll after inserting a new entry into the map. The loadFactor is set to 1
+ // to avoid resizing because. The accessOrder is set to true.
+ super(capacity + 1, 1, /*accessOrder=*/ true);
+ this.capacity = capacity;
+ }
+
+ // Users must call this method instead of put to keep count of the total number of entries
+ // inserted.
+ private void putAttribute(String key, AttributeValue value) {
+ totalRecordedAttributes += 1;
+ put(key, value);
+ }
+
+ // Users must call this method instead of putAll to keep count of the total number of entries
+ // inserted.
+ private void putAttributes(Map<String, AttributeValue> attributes) {
+ totalRecordedAttributes += attributes.size();
+ putAll(attributes);
+ }
+
+ private int getNumberOfDroppedAttributes() {
+ return totalRecordedAttributes - size();
+ }
+
+ // It is called after each put or putAll call in order to determine if the eldest inserted
+ // entry should be removed or not.
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<String, AttributeValue> eldest) {
+ return size() > this.capacity;
+ }
+ }
+
+ private static final class TraceEvents<T> {
+ private int totalRecordedEvents = 0;
+ private final EvictingQueue<T> events;
+
+ private int getNumberOfDroppedEvents() {
+ return totalRecordedEvents - events.size();
+ }
+
+ TraceEvents(int maxNumEvents) {
+ events = EvictingQueue.create(maxNumEvents);
+ }
+
+ void addEvent(T event) {
+ totalRecordedEvents++;
+ events.add(event);
+ }
+ }
+
+ // Timed event that uses nanoTime to represent the Timestamp.
+ private static final class EventWithNanoTime<T> {
+ private final long nanoTime;
+ private final T event;
+
+ private EventWithNanoTime(long nanoTime, T event) {
+ this.nanoTime = nanoTime;
+ this.event = event;
+ }
+
+ private TimedEvent<T> toSpanDataTimedEvent(TimestampConverter timestampConverter) {
+ return TimedEvent.create(timestampConverter.convertNanoTime(nanoTime), event);
+ }
+ }
+
+ private RecordEventsSpanImpl(
+ SpanContext context,
+ String name,
+ @Nullable Kind kind,
+ @Nullable SpanId parentSpanId,
+ @Nullable Boolean hasRemoteParent,
+ TraceParams traceParams,
+ StartEndHandler startEndHandler,
+ @Nullable TimestampConverter timestampConverter,
+ Clock clock) {
+ super(context, RECORD_EVENTS_SPAN_OPTIONS);
+ this.parentSpanId = parentSpanId;
+ this.hasRemoteParent = hasRemoteParent;
+ this.name = name;
+ this.kind = kind;
+ this.traceParams = traceParams;
+ this.startEndHandler = startEndHandler;
+ this.clock = clock;
+ this.hasBeenEnded = false;
+ this.sampleToLocalSpanStore = false;
+ this.timestampConverter =
+ timestampConverter != null ? timestampConverter : TimestampConverter.now(clock);
+ startNanoTime = clock.nowNanos();
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/SpanBuilderImpl.java b/impl_core/src/main/java/io/opencensus/implcore/trace/SpanBuilderImpl.java
new file mode 100644
index 00000000..5565e9de
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/SpanBuilderImpl.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import io.opencensus.common.Clock;
+import io.opencensus.implcore.internal.TimestampConverter;
+import io.opencensus.implcore.trace.internal.RandomHandler;
+import io.opencensus.trace.Link;
+import io.opencensus.trace.Link.Type;
+import io.opencensus.trace.Sampler;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.Span.Kind;
+import io.opencensus.trace.SpanBuilder;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.TraceId;
+import io.opencensus.trace.TraceOptions;
+import io.opencensus.trace.Tracestate;
+import io.opencensus.trace.config.TraceConfig;
+import io.opencensus.trace.config.TraceParams;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import javax.annotation.Nullable;
+
+/** Implementation of the {@link SpanBuilder}. */
+final class SpanBuilderImpl extends SpanBuilder {
+ private static final Tracestate TRACESTATE_DEFAULT = Tracestate.builder().build();
+
+ private static final TraceOptions SAMPLED_TRACE_OPTIONS =
+ TraceOptions.builder().setIsSampled(true).build();
+ private static final TraceOptions NOT_SAMPLED_TRACE_OPTIONS =
+ TraceOptions.builder().setIsSampled(false).build();
+
+ private final Options options;
+ private final String name;
+ @Nullable private final Span parent;
+ @Nullable private final SpanContext remoteParentSpanContext;
+ @Nullable private Sampler sampler;
+ private List<Span> parentLinks = Collections.<Span>emptyList();
+ @Nullable private Boolean recordEvents;
+ @Nullable private Kind kind;
+
+ private Span startSpanInternal(
+ @Nullable SpanContext parent,
+ @Nullable Boolean hasRemoteParent,
+ String name,
+ @Nullable Sampler sampler,
+ List<Span> parentLinks,
+ @Nullable Boolean recordEvents,
+ @Nullable Kind kind,
+ @Nullable TimestampConverter timestampConverter) {
+ TraceParams activeTraceParams = options.traceConfig.getActiveTraceParams();
+ Random random = options.randomHandler.current();
+ TraceId traceId;
+ SpanId spanId = SpanId.generateRandomId(random);
+ SpanId parentSpanId = null;
+ // TODO(bdrutu): Handle tracestate correctly not just propagate.
+ Tracestate tracestate = TRACESTATE_DEFAULT;
+ if (parent == null || !parent.isValid()) {
+ // New root span.
+ traceId = TraceId.generateRandomId(random);
+ // This is a root span so no remote or local parent.
+ hasRemoteParent = null;
+ } else {
+ // New child span.
+ traceId = parent.getTraceId();
+ parentSpanId = parent.getSpanId();
+ tracestate = parent.getTracestate();
+ }
+ TraceOptions traceOptions =
+ makeSamplingDecision(
+ parent,
+ hasRemoteParent,
+ name,
+ sampler,
+ parentLinks,
+ traceId,
+ spanId,
+ activeTraceParams)
+ ? SAMPLED_TRACE_OPTIONS
+ : NOT_SAMPLED_TRACE_OPTIONS;
+ Span span =
+ (traceOptions.isSampled() || Boolean.TRUE.equals(recordEvents))
+ ? RecordEventsSpanImpl.startSpan(
+ SpanContext.create(traceId, spanId, traceOptions, tracestate),
+ name,
+ kind,
+ parentSpanId,
+ hasRemoteParent,
+ activeTraceParams,
+ options.startEndHandler,
+ timestampConverter,
+ options.clock)
+ : NoRecordEventsSpanImpl.create(
+ SpanContext.create(traceId, spanId, traceOptions, tracestate));
+ linkSpans(span, parentLinks);
+ return span;
+ }
+
+ private static boolean makeSamplingDecision(
+ @Nullable SpanContext parent,
+ @Nullable Boolean hasRemoteParent,
+ String name,
+ @Nullable Sampler sampler,
+ List<Span> parentLinks,
+ TraceId traceId,
+ SpanId spanId,
+ TraceParams activeTraceParams) {
+ // If users set a specific sampler in the SpanBuilder, use it.
+ if (sampler != null) {
+ return sampler.shouldSample(parent, hasRemoteParent, traceId, spanId, name, parentLinks);
+ }
+ // Use the default sampler if this is a root Span or this is an entry point Span (has remote
+ // parent).
+ if (Boolean.TRUE.equals(hasRemoteParent) || parent == null || !parent.isValid()) {
+ return activeTraceParams
+ .getSampler()
+ .shouldSample(parent, hasRemoteParent, traceId, spanId, name, parentLinks);
+ }
+ // Parent is always different than null because otherwise we use the default sampler.
+ return parent.getTraceOptions().isSampled() || isAnyParentLinkSampled(parentLinks);
+ }
+
+ private static boolean isAnyParentLinkSampled(List<Span> parentLinks) {
+ for (Span parentLink : parentLinks) {
+ if (parentLink.getContext().getTraceOptions().isSampled()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static void linkSpans(Span span, List<Span> parentLinks) {
+ if (!parentLinks.isEmpty()) {
+ Link childLink = Link.fromSpanContext(span.getContext(), Type.CHILD_LINKED_SPAN);
+ for (Span linkedSpan : parentLinks) {
+ linkedSpan.addLink(childLink);
+ span.addLink(Link.fromSpanContext(linkedSpan.getContext(), Type.PARENT_LINKED_SPAN));
+ }
+ }
+ }
+
+ static SpanBuilderImpl createWithParent(String spanName, @Nullable Span parent, Options options) {
+ return new SpanBuilderImpl(spanName, null, parent, options);
+ }
+
+ static SpanBuilderImpl createWithRemoteParent(
+ String spanName, @Nullable SpanContext remoteParentSpanContext, Options options) {
+ return new SpanBuilderImpl(spanName, remoteParentSpanContext, null, options);
+ }
+
+ private SpanBuilderImpl(
+ String name,
+ @Nullable SpanContext remoteParentSpanContext,
+ @Nullable Span parent,
+ Options options) {
+ this.name = checkNotNull(name, "name");
+ this.parent = parent;
+ this.remoteParentSpanContext = remoteParentSpanContext;
+ this.options = options;
+ }
+
+ @Override
+ public Span startSpan() {
+ SpanContext parentContext = remoteParentSpanContext;
+ Boolean hasRemoteParent = Boolean.TRUE;
+ TimestampConverter timestampConverter = null;
+ if (remoteParentSpanContext == null) {
+ // This is not a child of a remote Span. Get the parent SpanContext from the parent Span if
+ // any.
+ Span parent = this.parent;
+ hasRemoteParent = Boolean.FALSE;
+ if (parent != null) {
+ parentContext = parent.getContext();
+ // Pass the timestamp converter from the parent to ensure that the recorded events are in
+ // the right order. Implementation uses System.nanoTime() which is monotonically increasing.
+ if (parent instanceof RecordEventsSpanImpl) {
+ timestampConverter = ((RecordEventsSpanImpl) parent).getTimestampConverter();
+ }
+ } else {
+ hasRemoteParent = null;
+ }
+ }
+ return startSpanInternal(
+ parentContext,
+ hasRemoteParent,
+ name,
+ sampler,
+ parentLinks,
+ recordEvents,
+ kind,
+ timestampConverter);
+ }
+
+ static final class Options {
+ private final RandomHandler randomHandler;
+ private final RecordEventsSpanImpl.StartEndHandler startEndHandler;
+ private final Clock clock;
+ private final TraceConfig traceConfig;
+
+ Options(
+ RandomHandler randomHandler,
+ RecordEventsSpanImpl.StartEndHandler startEndHandler,
+ Clock clock,
+ TraceConfig traceConfig) {
+ this.randomHandler = checkNotNull(randomHandler, "randomHandler");
+ this.startEndHandler = checkNotNull(startEndHandler, "startEndHandler");
+ this.clock = checkNotNull(clock, "clock");
+ this.traceConfig = checkNotNull(traceConfig, "traceConfig");
+ }
+ }
+
+ @Override
+ public SpanBuilderImpl setSampler(Sampler sampler) {
+ this.sampler = checkNotNull(sampler, "sampler");
+ return this;
+ }
+
+ @Override
+ public SpanBuilderImpl setParentLinks(List<Span> parentLinks) {
+ this.parentLinks = checkNotNull(parentLinks, "parentLinks");
+ return this;
+ }
+
+ @Override
+ public SpanBuilderImpl setRecordEvents(boolean recordEvents) {
+ this.recordEvents = recordEvents;
+ return this;
+ }
+
+ @Override
+ public SpanBuilderImpl setSpanKind(@Nullable Kind kind) {
+ this.kind = kind;
+ return this;
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/StartEndHandlerImpl.java b/impl_core/src/main/java/io/opencensus/implcore/trace/StartEndHandlerImpl.java
new file mode 100644
index 00000000..6adaa200
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/StartEndHandlerImpl.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace;
+
+import io.opencensus.implcore.internal.EventQueue;
+import io.opencensus.implcore.trace.RecordEventsSpanImpl.StartEndHandler;
+import io.opencensus.implcore.trace.export.RunningSpanStoreImpl;
+import io.opencensus.implcore.trace.export.SampledSpanStoreImpl;
+import io.opencensus.implcore.trace.export.SpanExporterImpl;
+import io.opencensus.trace.Span.Options;
+import io.opencensus.trace.export.SpanData;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Uses the provided {@link EventQueue} to defer processing/exporting of the {@link SpanData} to
+ * avoid impacting the critical path.
+ */
+@ThreadSafe
+public final class StartEndHandlerImpl implements StartEndHandler {
+ private final SpanExporterImpl spanExporter;
+ @Nullable private final RunningSpanStoreImpl runningSpanStore;
+ @Nullable private final SampledSpanStoreImpl sampledSpanStore;
+ private final EventQueue eventQueue;
+ // true if any of (runningSpanStore OR sampledSpanStore) are different than null, which
+ // means the spans with RECORD_EVENTS should be enqueued in the queue.
+ private final boolean enqueueEventForNonSampledSpans;
+
+ /**
+ * Constructs a new {@code StartEndHandlerImpl}.
+ *
+ * @param spanExporter the {@code SpanExporter} implementation.
+ * @param runningSpanStore the {@code RunningSpanStore} implementation.
+ * @param sampledSpanStore the {@code SampledSpanStore} implementation.
+ * @param eventQueue the event queue where all the events are enqueued.
+ */
+ public StartEndHandlerImpl(
+ SpanExporterImpl spanExporter,
+ @Nullable RunningSpanStoreImpl runningSpanStore,
+ @Nullable SampledSpanStoreImpl sampledSpanStore,
+ EventQueue eventQueue) {
+ this.spanExporter = spanExporter;
+ this.runningSpanStore = runningSpanStore;
+ this.sampledSpanStore = sampledSpanStore;
+ this.enqueueEventForNonSampledSpans = runningSpanStore != null || sampledSpanStore != null;
+ this.eventQueue = eventQueue;
+ }
+
+ @Override
+ public void onStart(RecordEventsSpanImpl span) {
+ if (span.getOptions().contains(Options.RECORD_EVENTS) && enqueueEventForNonSampledSpans) {
+ eventQueue.enqueue(new SpanStartEvent(span, runningSpanStore));
+ }
+ }
+
+ @Override
+ public void onEnd(RecordEventsSpanImpl span) {
+ if ((span.getOptions().contains(Options.RECORD_EVENTS) && enqueueEventForNonSampledSpans)
+ || span.getContext().getTraceOptions().isSampled()) {
+ eventQueue.enqueue(new SpanEndEvent(span, spanExporter, runningSpanStore, sampledSpanStore));
+ }
+ }
+
+ // An EventQueue entry that records the start of the span event.
+ private static final class SpanStartEvent implements EventQueue.Entry {
+ private final RecordEventsSpanImpl span;
+ @Nullable private final RunningSpanStoreImpl activeSpansExporter;
+
+ SpanStartEvent(RecordEventsSpanImpl span, @Nullable RunningSpanStoreImpl activeSpansExporter) {
+ this.span = span;
+ this.activeSpansExporter = activeSpansExporter;
+ }
+
+ @Override
+ public void process() {
+ if (activeSpansExporter != null) {
+ activeSpansExporter.onStart(span);
+ }
+ }
+ }
+
+ // An EventQueue entry that records the end of the span event.
+ private static final class SpanEndEvent implements EventQueue.Entry {
+ private final RecordEventsSpanImpl span;
+ @Nullable private final RunningSpanStoreImpl runningSpanStore;
+ private final SpanExporterImpl spanExporter;
+ @Nullable private final SampledSpanStoreImpl sampledSpanStore;
+
+ SpanEndEvent(
+ RecordEventsSpanImpl span,
+ SpanExporterImpl spanExporter,
+ @Nullable RunningSpanStoreImpl runningSpanStore,
+ @Nullable SampledSpanStoreImpl sampledSpanStore) {
+ this.span = span;
+ this.runningSpanStore = runningSpanStore;
+ this.spanExporter = spanExporter;
+ this.sampledSpanStore = sampledSpanStore;
+ }
+
+ @Override
+ public void process() {
+ if (span.getContext().getTraceOptions().isSampled()) {
+ spanExporter.addSpan(span);
+ }
+ if (runningSpanStore != null) {
+ runningSpanStore.onEnd(span);
+ }
+ if (sampledSpanStore != null) {
+ sampledSpanStore.considerForSampling(span);
+ }
+ }
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/TraceComponentImplBase.java b/impl_core/src/main/java/io/opencensus/implcore/trace/TraceComponentImplBase.java
new file mode 100644
index 00000000..c1432432
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/TraceComponentImplBase.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace;
+
+import io.opencensus.common.Clock;
+import io.opencensus.implcore.internal.EventQueue;
+import io.opencensus.implcore.internal.SimpleEventQueue;
+import io.opencensus.implcore.trace.RecordEventsSpanImpl.StartEndHandler;
+import io.opencensus.implcore.trace.config.TraceConfigImpl;
+import io.opencensus.implcore.trace.export.ExportComponentImpl;
+import io.opencensus.implcore.trace.internal.RandomHandler;
+import io.opencensus.implcore.trace.propagation.PropagationComponentImpl;
+import io.opencensus.trace.TraceComponent;
+import io.opencensus.trace.Tracer;
+import io.opencensus.trace.config.TraceConfig;
+import io.opencensus.trace.export.ExportComponent;
+import io.opencensus.trace.propagation.PropagationComponent;
+
+/**
+ * Helper class to allow sharing the code for all the {@link TraceComponent} implementations. This
+ * class cannot use inheritance because in version 0.5.* the constructor of the {@code
+ * TraceComponent} is package protected.
+ *
+ * <p>This can be changed back to inheritance when version 0.5.* is no longer supported.
+ */
+public final class TraceComponentImplBase {
+ private final ExportComponentImpl exportComponent;
+ private final PropagationComponent propagationComponent = new PropagationComponentImpl();
+ private final Clock clock;
+ private final TraceConfig traceConfig = new TraceConfigImpl();
+ private final Tracer tracer;
+
+ /**
+ * Creates a new {@code TraceComponentImplBase}.
+ *
+ * @param clock the clock to use throughout tracing.
+ * @param randomHandler the random number generator for generating trace and span IDs.
+ * @param eventQueue the queue implementation.
+ */
+ public TraceComponentImplBase(Clock clock, RandomHandler randomHandler, EventQueue eventQueue) {
+ this.clock = clock;
+ // TODO(bdrutu): Add a config/argument for supportInProcessStores.
+ if (eventQueue instanceof SimpleEventQueue) {
+ exportComponent = ExportComponentImpl.createWithoutInProcessStores(eventQueue);
+ } else {
+ exportComponent = ExportComponentImpl.createWithInProcessStores(eventQueue);
+ }
+ StartEndHandler startEndHandler =
+ new StartEndHandlerImpl(
+ exportComponent.getSpanExporter(),
+ exportComponent.getRunningSpanStore(),
+ exportComponent.getSampledSpanStore(),
+ eventQueue);
+ tracer = new TracerImpl(randomHandler, startEndHandler, clock, traceConfig);
+ }
+
+ public Tracer getTracer() {
+ return tracer;
+ }
+
+ public PropagationComponent getPropagationComponent() {
+ return propagationComponent;
+ }
+
+ public final Clock getClock() {
+ return clock;
+ }
+
+ public ExportComponent getExportComponent() {
+ return exportComponent;
+ }
+
+ public TraceConfig getTraceConfig() {
+ return traceConfig;
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/TracerImpl.java b/impl_core/src/main/java/io/opencensus/implcore/trace/TracerImpl.java
new file mode 100644
index 00000000..48df8055
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/TracerImpl.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace;
+
+import io.opencensus.common.Clock;
+import io.opencensus.implcore.trace.internal.RandomHandler;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.SpanBuilder;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.Tracer;
+import io.opencensus.trace.config.TraceConfig;
+import javax.annotation.Nullable;
+
+/** Implementation of the {@link Tracer}. */
+public final class TracerImpl extends Tracer {
+ private final SpanBuilderImpl.Options spanBuilderOptions;
+
+ TracerImpl(
+ RandomHandler randomHandler,
+ RecordEventsSpanImpl.StartEndHandler startEndHandler,
+ Clock clock,
+ TraceConfig traceConfig) {
+ spanBuilderOptions =
+ new SpanBuilderImpl.Options(randomHandler, startEndHandler, clock, traceConfig);
+ }
+
+ @Override
+ public SpanBuilder spanBuilderWithExplicitParent(String spanName, @Nullable Span parent) {
+ return SpanBuilderImpl.createWithParent(spanName, parent, spanBuilderOptions);
+ }
+
+ @Override
+ public SpanBuilder spanBuilderWithRemoteParent(
+ String spanName, @Nullable SpanContext remoteParentSpanContext) {
+ return SpanBuilderImpl.createWithRemoteParent(
+ spanName, remoteParentSpanContext, spanBuilderOptions);
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/config/TraceConfigImpl.java b/impl_core/src/main/java/io/opencensus/implcore/trace/config/TraceConfigImpl.java
new file mode 100644
index 00000000..25f0c613
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/config/TraceConfigImpl.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace.config;
+
+import io.opencensus.trace.config.TraceConfig;
+import io.opencensus.trace.config.TraceParams;
+
+/**
+ * Global configuration of the trace service. This allows users to change configs for the default
+ * sampler, maximum events to be kept, etc.
+ */
+public final class TraceConfigImpl extends TraceConfig {
+ // Reads and writes are atomic for reference variables. Use volatile to ensure that these
+ // operations are visible on other CPUs as well.
+ private volatile TraceParams activeTraceParams = TraceParams.DEFAULT;
+
+ /** Constructs a new {@code TraceConfigImpl}. */
+ public TraceConfigImpl() {}
+
+ @Override
+ public TraceParams getActiveTraceParams() {
+ return activeTraceParams;
+ }
+
+ @Override
+ public void updateActiveTraceParams(TraceParams traceParams) {
+ activeTraceParams = traceParams;
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/export/ExportComponentImpl.java b/impl_core/src/main/java/io/opencensus/implcore/trace/export/ExportComponentImpl.java
new file mode 100644
index 00000000..19817380
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/export/ExportComponentImpl.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace.export;
+
+import io.opencensus.common.Duration;
+import io.opencensus.implcore.internal.EventQueue;
+import io.opencensus.trace.export.ExportComponent;
+import io.opencensus.trace.export.RunningSpanStore;
+import io.opencensus.trace.export.SampledSpanStore;
+
+/** Implementation of the {@link ExportComponent}. */
+public final class ExportComponentImpl extends ExportComponent {
+ private static final int EXPORTER_BUFFER_SIZE = 32;
+ // Enforces that trace export exports data at least once every 5 seconds.
+ private static final Duration EXPORTER_SCHEDULE_DELAY = Duration.create(5, 0);
+
+ private final SpanExporterImpl spanExporter;
+ private final RunningSpanStoreImpl runningSpanStore;
+ private final SampledSpanStoreImpl sampledSpanStore;
+
+ @Override
+ public SpanExporterImpl getSpanExporter() {
+ return spanExporter;
+ }
+
+ @Override
+ public RunningSpanStoreImpl getRunningSpanStore() {
+ return runningSpanStore;
+ }
+
+ @Override
+ public SampledSpanStoreImpl getSampledSpanStore() {
+ return sampledSpanStore;
+ }
+
+ @Override
+ public void shutdown() {
+ sampledSpanStore.shutdown();
+ spanExporter.shutdown();
+ }
+
+ /**
+ * Returns a new {@code ExportComponentImpl} that has valid instances for {@link RunningSpanStore}
+ * and {@link SampledSpanStore}.
+ *
+ * @return a new {@code ExportComponentImpl}.
+ */
+ public static ExportComponentImpl createWithInProcessStores(EventQueue eventQueue) {
+ return new ExportComponentImpl(true, eventQueue);
+ }
+
+ /**
+ * Returns a new {@code ExportComponentImpl} that has {@code null} instances for {@link
+ * RunningSpanStore} and {@link SampledSpanStore}.
+ *
+ * @return a new {@code ExportComponentImpl}.
+ */
+ public static ExportComponentImpl createWithoutInProcessStores(EventQueue eventQueue) {
+ return new ExportComponentImpl(false, eventQueue);
+ }
+
+ /**
+ * Constructs a new {@code ExportComponentImpl}.
+ *
+ * @param supportInProcessStores {@code true} to instantiate {@link RunningSpanStore} and {@link
+ * SampledSpanStore}.
+ */
+ private ExportComponentImpl(boolean supportInProcessStores, EventQueue eventQueue) {
+ this.spanExporter = SpanExporterImpl.create(EXPORTER_BUFFER_SIZE, EXPORTER_SCHEDULE_DELAY);
+ this.runningSpanStore =
+ supportInProcessStores
+ ? new InProcessRunningSpanStoreImpl()
+ : RunningSpanStoreImpl.getNoopRunningSpanStoreImpl();
+ this.sampledSpanStore =
+ supportInProcessStores
+ ? new InProcessSampledSpanStoreImpl(eventQueue)
+ : SampledSpanStoreImpl.getNoopSampledSpanStoreImpl();
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/export/InProcessRunningSpanStoreImpl.java b/impl_core/src/main/java/io/opencensus/implcore/trace/export/InProcessRunningSpanStoreImpl.java
new file mode 100644
index 00000000..f7aeac71
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/export/InProcessRunningSpanStoreImpl.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace.export;
+
+import io.opencensus.implcore.trace.RecordEventsSpanImpl;
+import io.opencensus.implcore.trace.internal.ConcurrentIntrusiveList;
+import io.opencensus.trace.export.RunningSpanStore;
+import io.opencensus.trace.export.SpanData;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.concurrent.ThreadSafe;
+
+/** In-process implementation of the {@link RunningSpanStore}. */
+@ThreadSafe
+public final class InProcessRunningSpanStoreImpl extends RunningSpanStoreImpl {
+ private final ConcurrentIntrusiveList<RecordEventsSpanImpl> runningSpans;
+
+ public InProcessRunningSpanStoreImpl() {
+ runningSpans = new ConcurrentIntrusiveList<RecordEventsSpanImpl>();
+ }
+
+ @Override
+ public void onStart(RecordEventsSpanImpl span) {
+ runningSpans.addElement(span);
+ }
+
+ @Override
+ public void onEnd(RecordEventsSpanImpl span) {
+ runningSpans.removeElement(span);
+ }
+
+ @Override
+ public Summary getSummary() {
+ Collection<RecordEventsSpanImpl> allRunningSpans = runningSpans.getAll();
+ Map<String, Integer> numSpansPerName = new HashMap<String, Integer>();
+ for (RecordEventsSpanImpl span : allRunningSpans) {
+ Integer prevValue = numSpansPerName.get(span.getName());
+ numSpansPerName.put(span.getName(), prevValue != null ? prevValue + 1 : 1);
+ }
+ Map<String, PerSpanNameSummary> perSpanNameSummary = new HashMap<String, PerSpanNameSummary>();
+ for (Map.Entry<String, Integer> it : numSpansPerName.entrySet()) {
+ perSpanNameSummary.put(it.getKey(), PerSpanNameSummary.create(it.getValue()));
+ }
+ Summary summary = Summary.create(perSpanNameSummary);
+ return summary;
+ }
+
+ @Override
+ public Collection<SpanData> getRunningSpans(Filter filter) {
+ Collection<RecordEventsSpanImpl> allRunningSpans = runningSpans.getAll();
+ int maxSpansToReturn =
+ filter.getMaxSpansToReturn() == 0 ? allRunningSpans.size() : filter.getMaxSpansToReturn();
+ List<SpanData> ret = new ArrayList<SpanData>(maxSpansToReturn);
+ for (RecordEventsSpanImpl span : allRunningSpans) {
+ if (ret.size() == maxSpansToReturn) {
+ break;
+ }
+ if (span.getName().equals(filter.getSpanName())) {
+ ret.add(span.toSpanData());
+ }
+ }
+ return ret;
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/export/InProcessSampledSpanStoreImpl.java b/impl_core/src/main/java/io/opencensus/implcore/trace/export/InProcessSampledSpanStoreImpl.java
new file mode 100644
index 00000000..0d8e493b
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/export/InProcessSampledSpanStoreImpl.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace.export;
+
+import com.google.common.collect.EvictingQueue;
+import io.opencensus.implcore.internal.EventQueue;
+import io.opencensus.implcore.trace.RecordEventsSpanImpl;
+import io.opencensus.trace.Status;
+import io.opencensus.trace.Status.CanonicalCode;
+import io.opencensus.trace.export.SampledSpanStore;
+import io.opencensus.trace.export.SpanData;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
+/** In-process implementation of the {@link SampledSpanStore}. */
+@ThreadSafe
+public final class InProcessSampledSpanStoreImpl extends SampledSpanStoreImpl {
+ private static final int NUM_SAMPLES_PER_LATENCY_BUCKET = 10;
+ private static final int NUM_SAMPLES_PER_ERROR_BUCKET = 5;
+ private static final long TIME_BETWEEN_SAMPLES = TimeUnit.SECONDS.toNanos(1);
+ private static final int NUM_LATENCY_BUCKETS = LatencyBucketBoundaries.values().length;
+ // The total number of canonical codes - 1 (the OK code).
+ private static final int NUM_ERROR_BUCKETS = CanonicalCode.values().length - 1;
+ private static final int MAX_PER_SPAN_NAME_SAMPLES =
+ NUM_SAMPLES_PER_LATENCY_BUCKET * NUM_LATENCY_BUCKETS
+ + NUM_SAMPLES_PER_ERROR_BUCKET * NUM_ERROR_BUCKETS;
+
+ // Used to stream the register/unregister events to the implementation to avoid lock contention
+ // between the main threads and the worker thread.
+ private final EventQueue eventQueue;
+
+ @GuardedBy("samples")
+ private final Map<String, PerSpanNameSamples> samples;
+
+ private static final class Bucket {
+
+ private final EvictingQueue<RecordEventsSpanImpl> sampledSpansQueue;
+ private final EvictingQueue<RecordEventsSpanImpl> notSampledSpansQueue;
+ private long lastSampledNanoTime;
+ private long lastNotSampledNanoTime;
+
+ private Bucket(int numSamples) {
+ sampledSpansQueue = EvictingQueue.create(numSamples);
+ notSampledSpansQueue = EvictingQueue.create(numSamples);
+ }
+
+ private void considerForSampling(RecordEventsSpanImpl span) {
+ long spanEndNanoTime = span.getEndNanoTime();
+ if (span.getContext().getTraceOptions().isSampled()) {
+ // Need to compare by doing the subtraction all the time because in case of an overflow,
+ // this may never sample again (at least for the next ~200 years). No real chance to
+ // overflow two times because that means the process runs for ~200 years.
+ if (spanEndNanoTime - lastSampledNanoTime > TIME_BETWEEN_SAMPLES) {
+ sampledSpansQueue.add(span);
+ lastSampledNanoTime = spanEndNanoTime;
+ }
+ } else {
+ // Need to compare by doing the subtraction all the time because in case of an overflow,
+ // this may never sample again (at least for the next ~200 years). No real chance to
+ // overflow two times because that means the process runs for ~200 years.
+ if (spanEndNanoTime - lastNotSampledNanoTime > TIME_BETWEEN_SAMPLES) {
+ notSampledSpansQueue.add(span);
+ lastNotSampledNanoTime = spanEndNanoTime;
+ }
+ }
+ }
+
+ private void getSamples(int maxSpansToReturn, List<RecordEventsSpanImpl> output) {
+ getSamples(maxSpansToReturn, output, sampledSpansQueue);
+ getSamples(maxSpansToReturn, output, notSampledSpansQueue);
+ }
+
+ private static void getSamples(
+ int maxSpansToReturn,
+ List<RecordEventsSpanImpl> output,
+ EvictingQueue<RecordEventsSpanImpl> queue) {
+ for (RecordEventsSpanImpl span : queue) {
+ if (output.size() >= maxSpansToReturn) {
+ break;
+ }
+ output.add(span);
+ }
+ }
+
+ private void getSamplesFilteredByLatency(
+ long latencyLowerNs,
+ long latencyUpperNs,
+ int maxSpansToReturn,
+ List<RecordEventsSpanImpl> output) {
+ getSamplesFilteredByLatency(
+ latencyLowerNs, latencyUpperNs, maxSpansToReturn, output, sampledSpansQueue);
+ getSamplesFilteredByLatency(
+ latencyLowerNs, latencyUpperNs, maxSpansToReturn, output, notSampledSpansQueue);
+ }
+
+ private static void getSamplesFilteredByLatency(
+ long latencyLowerNs,
+ long latencyUpperNs,
+ int maxSpansToReturn,
+ List<RecordEventsSpanImpl> output,
+ EvictingQueue<RecordEventsSpanImpl> queue) {
+ for (RecordEventsSpanImpl span : queue) {
+ if (output.size() >= maxSpansToReturn) {
+ break;
+ }
+ long spanLatencyNs = span.getLatencyNs();
+ if (spanLatencyNs >= latencyLowerNs && spanLatencyNs < latencyUpperNs) {
+ output.add(span);
+ }
+ }
+ }
+
+ private int getNumSamples() {
+ return sampledSpansQueue.size() + notSampledSpansQueue.size();
+ }
+ }
+
+ /**
+ * Keeps samples for a given span name. Samples for all the latency buckets and for all canonical
+ * codes other than OK.
+ */
+ private static final class PerSpanNameSamples {
+
+ private final Bucket[] latencyBuckets;
+ private final Bucket[] errorBuckets;
+
+ private PerSpanNameSamples() {
+ latencyBuckets = new Bucket[NUM_LATENCY_BUCKETS];
+ for (int i = 0; i < NUM_LATENCY_BUCKETS; i++) {
+ latencyBuckets[i] = new Bucket(NUM_SAMPLES_PER_LATENCY_BUCKET);
+ }
+ errorBuckets = new Bucket[NUM_ERROR_BUCKETS];
+ for (int i = 0; i < NUM_ERROR_BUCKETS; i++) {
+ errorBuckets[i] = new Bucket(NUM_SAMPLES_PER_ERROR_BUCKET);
+ }
+ }
+
+ @Nullable
+ private Bucket getLatencyBucket(long latencyNs) {
+ for (int i = 0; i < NUM_LATENCY_BUCKETS; i++) {
+ LatencyBucketBoundaries boundaries = LatencyBucketBoundaries.values()[i];
+ if (latencyNs >= boundaries.getLatencyLowerNs()
+ && latencyNs < boundaries.getLatencyUpperNs()) {
+ return latencyBuckets[i];
+ }
+ }
+ // latencyNs is negative or Long.MAX_VALUE, so this Span can be ignored. This cannot happen
+ // in real production because System#nanoTime is monotonic.
+ return null;
+ }
+
+ private Bucket getErrorBucket(CanonicalCode code) {
+ return errorBuckets[code.value() - 1];
+ }
+
+ private void considerForSampling(RecordEventsSpanImpl span) {
+ Status status = span.getStatus();
+ // Null status means running Span, this should not happen in production, but the library
+ // should not crash because of this.
+ if (status != null) {
+ Bucket bucket =
+ status.isOk()
+ ? getLatencyBucket(span.getLatencyNs())
+ : getErrorBucket(status.getCanonicalCode());
+ // If unable to find the bucket, ignore this Span.
+ if (bucket != null) {
+ bucket.considerForSampling(span);
+ }
+ }
+ }
+
+ private Map<LatencyBucketBoundaries, Integer> getNumbersOfLatencySampledSpans() {
+ Map<LatencyBucketBoundaries, Integer> latencyBucketSummaries =
+ new EnumMap<LatencyBucketBoundaries, Integer>(LatencyBucketBoundaries.class);
+ for (int i = 0; i < NUM_LATENCY_BUCKETS; i++) {
+ latencyBucketSummaries.put(
+ LatencyBucketBoundaries.values()[i], latencyBuckets[i].getNumSamples());
+ }
+ return latencyBucketSummaries;
+ }
+
+ private Map<CanonicalCode, Integer> getNumbersOfErrorSampledSpans() {
+ Map<CanonicalCode, Integer> errorBucketSummaries =
+ new EnumMap<CanonicalCode, Integer>(CanonicalCode.class);
+ for (int i = 0; i < NUM_ERROR_BUCKETS; i++) {
+ errorBucketSummaries.put(CanonicalCode.values()[i + 1], errorBuckets[i].getNumSamples());
+ }
+ return errorBucketSummaries;
+ }
+
+ private List<RecordEventsSpanImpl> getErrorSamples(
+ @Nullable CanonicalCode code, int maxSpansToReturn) {
+ ArrayList<RecordEventsSpanImpl> output =
+ new ArrayList<RecordEventsSpanImpl>(maxSpansToReturn);
+ if (code != null) {
+ getErrorBucket(code).getSamples(maxSpansToReturn, output);
+ } else {
+ for (int i = 0; i < NUM_ERROR_BUCKETS; i++) {
+ errorBuckets[i].getSamples(maxSpansToReturn, output);
+ }
+ }
+ return output;
+ }
+
+ private List<RecordEventsSpanImpl> getLatencySamples(
+ long latencyLowerNs, long latencyUpperNs, int maxSpansToReturn) {
+ ArrayList<RecordEventsSpanImpl> output =
+ new ArrayList<RecordEventsSpanImpl>(maxSpansToReturn);
+ for (int i = 0; i < NUM_LATENCY_BUCKETS; i++) {
+ LatencyBucketBoundaries boundaries = LatencyBucketBoundaries.values()[i];
+ if (latencyUpperNs >= boundaries.getLatencyLowerNs()
+ && latencyLowerNs < boundaries.getLatencyUpperNs()) {
+ latencyBuckets[i].getSamplesFilteredByLatency(
+ latencyLowerNs, latencyUpperNs, maxSpansToReturn, output);
+ }
+ }
+ return output;
+ }
+ }
+
+ /** Constructs a new {@code InProcessSampledSpanStoreImpl}. */
+ InProcessSampledSpanStoreImpl(EventQueue eventQueue) {
+ samples = new HashMap<String, PerSpanNameSamples>();
+ this.eventQueue = eventQueue;
+ }
+
+ @Override
+ public Summary getSummary() {
+ Map<String, PerSpanNameSummary> ret = new HashMap<String, PerSpanNameSummary>();
+ synchronized (samples) {
+ for (Map.Entry<String, PerSpanNameSamples> it : samples.entrySet()) {
+ ret.put(
+ it.getKey(),
+ PerSpanNameSummary.create(
+ it.getValue().getNumbersOfLatencySampledSpans(),
+ it.getValue().getNumbersOfErrorSampledSpans()));
+ }
+ }
+ return Summary.create(ret);
+ }
+
+ @Override
+ public void considerForSampling(RecordEventsSpanImpl span) {
+ synchronized (samples) {
+ String spanName = span.getName();
+ if (span.getSampleToLocalSpanStore() && !samples.containsKey(spanName)) {
+ samples.put(spanName, new PerSpanNameSamples());
+ }
+ PerSpanNameSamples perSpanNameSamples = samples.get(spanName);
+ if (perSpanNameSamples != null) {
+ perSpanNameSamples.considerForSampling(span);
+ }
+ }
+ }
+
+ @Override
+ public void registerSpanNamesForCollection(Collection<String> spanNames) {
+ eventQueue.enqueue(new RegisterSpanNameEvent(this, spanNames));
+ }
+
+ @Override
+ protected void shutdown() {
+ eventQueue.shutdown();
+ }
+
+ private void internaltRegisterSpanNamesForCollection(Collection<String> spanNames) {
+ synchronized (samples) {
+ for (String spanName : spanNames) {
+ if (!samples.containsKey(spanName)) {
+ samples.put(spanName, new PerSpanNameSamples());
+ }
+ }
+ }
+ }
+
+ private static final class RegisterSpanNameEvent implements EventQueue.Entry {
+ private final InProcessSampledSpanStoreImpl sampledSpanStore;
+ private final Collection<String> spanNames;
+
+ private RegisterSpanNameEvent(
+ InProcessSampledSpanStoreImpl sampledSpanStore, Collection<String> spanNames) {
+ this.sampledSpanStore = sampledSpanStore;
+ this.spanNames = new ArrayList<String>(spanNames);
+ }
+
+ @Override
+ public void process() {
+ sampledSpanStore.internaltRegisterSpanNamesForCollection(spanNames);
+ }
+ }
+
+ @Override
+ public void unregisterSpanNamesForCollection(Collection<String> spanNames) {
+ eventQueue.enqueue(new UnregisterSpanNameEvent(this, spanNames));
+ }
+
+ private void internalUnregisterSpanNamesForCollection(Collection<String> spanNames) {
+ synchronized (samples) {
+ samples.keySet().removeAll(spanNames);
+ }
+ }
+
+ private static final class UnregisterSpanNameEvent implements EventQueue.Entry {
+ private final InProcessSampledSpanStoreImpl sampledSpanStore;
+ private final Collection<String> spanNames;
+
+ private UnregisterSpanNameEvent(
+ InProcessSampledSpanStoreImpl sampledSpanStore, Collection<String> spanNames) {
+ this.sampledSpanStore = sampledSpanStore;
+ this.spanNames = new ArrayList<String>(spanNames);
+ }
+
+ @Override
+ public void process() {
+ sampledSpanStore.internalUnregisterSpanNamesForCollection(spanNames);
+ }
+ }
+
+ @Override
+ public Set<String> getRegisteredSpanNamesForCollection() {
+ synchronized (samples) {
+ return Collections.unmodifiableSet(new HashSet<String>(samples.keySet()));
+ }
+ }
+
+ @Override
+ public Collection<SpanData> getErrorSampledSpans(ErrorFilter filter) {
+ int numSpansToReturn =
+ filter.getMaxSpansToReturn() == 0
+ ? MAX_PER_SPAN_NAME_SAMPLES
+ : filter.getMaxSpansToReturn();
+ List<RecordEventsSpanImpl> spans = Collections.emptyList();
+ // Try to not keep the lock to much, do the RecordEventsSpanImpl -> SpanData conversion outside
+ // the lock.
+ synchronized (samples) {
+ PerSpanNameSamples perSpanNameSamples = samples.get(filter.getSpanName());
+ if (perSpanNameSamples != null) {
+ spans = perSpanNameSamples.getErrorSamples(filter.getCanonicalCode(), numSpansToReturn);
+ }
+ }
+ List<SpanData> ret = new ArrayList<SpanData>(spans.size());
+ for (RecordEventsSpanImpl span : spans) {
+ ret.add(span.toSpanData());
+ }
+ return Collections.unmodifiableList(ret);
+ }
+
+ @Override
+ public Collection<SpanData> getLatencySampledSpans(LatencyFilter filter) {
+ int numSpansToReturn =
+ filter.getMaxSpansToReturn() == 0
+ ? MAX_PER_SPAN_NAME_SAMPLES
+ : filter.getMaxSpansToReturn();
+ List<RecordEventsSpanImpl> spans = Collections.emptyList();
+ // Try to not keep the lock to much, do the RecordEventsSpanImpl -> SpanData conversion outside
+ // the lock.
+ synchronized (samples) {
+ PerSpanNameSamples perSpanNameSamples = samples.get(filter.getSpanName());
+ if (perSpanNameSamples != null) {
+ spans =
+ perSpanNameSamples.getLatencySamples(
+ filter.getLatencyLowerNs(), filter.getLatencyUpperNs(), numSpansToReturn);
+ }
+ }
+ List<SpanData> ret = new ArrayList<SpanData>(spans.size());
+ for (RecordEventsSpanImpl span : spans) {
+ ret.add(span.toSpanData());
+ }
+ return Collections.unmodifiableList(ret);
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/export/RunningSpanStoreImpl.java b/impl_core/src/main/java/io/opencensus/implcore/trace/export/RunningSpanStoreImpl.java
new file mode 100644
index 00000000..962f5b01
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/export/RunningSpanStoreImpl.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace.export;
+
+import io.opencensus.implcore.trace.RecordEventsSpanImpl;
+import io.opencensus.trace.export.RunningSpanStore;
+import io.opencensus.trace.export.SpanData;
+import java.util.Collection;
+import java.util.Collections;
+
+/** Abstract implementation of the {@link RunningSpanStore}. */
+public abstract class RunningSpanStoreImpl extends RunningSpanStore {
+
+ private static final RunningSpanStoreImpl NOOP_RUNNING_SPAN_STORE_IMPL =
+ new NoopRunningSpanStoreImpl();
+
+ /** Returns the no-op implementation of the {@link RunningSpanStoreImpl}. */
+ static RunningSpanStoreImpl getNoopRunningSpanStoreImpl() {
+ return NOOP_RUNNING_SPAN_STORE_IMPL;
+ }
+
+ /**
+ * Adds the {@code Span} into the running spans list when the {@code Span} starts.
+ *
+ * @param span the {@code Span} that started.
+ */
+ public abstract void onStart(RecordEventsSpanImpl span);
+
+ /**
+ * Removes the {@code Span} from the running spans list when the {@code Span} ends.
+ *
+ * @param span the {@code Span} that ended.
+ */
+ public abstract void onEnd(RecordEventsSpanImpl span);
+
+ private static final class NoopRunningSpanStoreImpl extends RunningSpanStoreImpl {
+
+ private static final Summary EMPTY_SUMMARY =
+ RunningSpanStore.Summary.create(Collections.<String, PerSpanNameSummary>emptyMap());
+
+ @Override
+ public void onStart(RecordEventsSpanImpl span) {}
+
+ @Override
+ public void onEnd(RecordEventsSpanImpl span) {}
+
+ @Override
+ public Summary getSummary() {
+ return EMPTY_SUMMARY;
+ }
+
+ @Override
+ public Collection<SpanData> getRunningSpans(Filter filter) {
+ return Collections.<SpanData>emptyList();
+ }
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/export/SampledSpanStoreImpl.java b/impl_core/src/main/java/io/opencensus/implcore/trace/export/SampledSpanStoreImpl.java
new file mode 100644
index 00000000..e67c2f8e
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/export/SampledSpanStoreImpl.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace.export;
+
+import io.opencensus.implcore.trace.RecordEventsSpanImpl;
+import io.opencensus.trace.export.SampledSpanStore;
+import io.opencensus.trace.export.SpanData;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+/** Abstract implementation of the {@link SampledSpanStore}. */
+public abstract class SampledSpanStoreImpl extends SampledSpanStore {
+ private static final SampledSpanStoreImpl NOOP_SAMPLED_SPAN_STORE_IMPL =
+ new NoopSampledSpanStoreImpl();
+
+ /** Returns the new no-op implmentation of {@link SampledSpanStoreImpl}. */
+ public static SampledSpanStoreImpl getNoopSampledSpanStoreImpl() {
+ return NOOP_SAMPLED_SPAN_STORE_IMPL;
+ }
+
+ /**
+ * Considers to save the given spans to the stored samples. This must be called at the end of each
+ * Span with the option RECORD_EVENTS.
+ *
+ * @param span the span to be consider for storing into the store buckets.
+ */
+ public abstract void considerForSampling(RecordEventsSpanImpl span);
+
+ protected void shutdown() {}
+
+ private static final class NoopSampledSpanStoreImpl extends SampledSpanStoreImpl {
+ private static final Summary EMPTY_SUMMARY =
+ Summary.create(Collections.<String, PerSpanNameSummary>emptyMap());
+ private static final Set<String> EMPTY_REGISTERED_SPAN_NAMES = Collections.<String>emptySet();
+ private static final Collection<SpanData> EMPTY_SPANDATA = Collections.<SpanData>emptySet();
+
+ @Override
+ public Summary getSummary() {
+ return EMPTY_SUMMARY;
+ }
+
+ @Override
+ public void considerForSampling(RecordEventsSpanImpl span) {}
+
+ @Override
+ public void registerSpanNamesForCollection(Collection<String> spanNames) {}
+
+ @Override
+ public void unregisterSpanNamesForCollection(Collection<String> spanNames) {}
+
+ @Override
+ public Set<String> getRegisteredSpanNamesForCollection() {
+ return EMPTY_REGISTERED_SPAN_NAMES;
+ }
+
+ @Override
+ public Collection<SpanData> getErrorSampledSpans(ErrorFilter filter) {
+ return EMPTY_SPANDATA;
+ }
+
+ @Override
+ public Collection<SpanData> getLatencySampledSpans(LatencyFilter filter) {
+ return EMPTY_SPANDATA;
+ }
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/export/SpanExporterImpl.java b/impl_core/src/main/java/io/opencensus/implcore/trace/export/SpanExporterImpl.java
new file mode 100644
index 00000000..51a7b05c
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/export/SpanExporterImpl.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace.export;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.opencensus.common.Duration;
+import io.opencensus.implcore.internal.DaemonThreadFactory;
+import io.opencensus.implcore.trace.RecordEventsSpanImpl;
+import io.opencensus.trace.export.ExportComponent;
+import io.opencensus.trace.export.SpanData;
+import io.opencensus.trace.export.SpanExporter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.concurrent.GuardedBy;
+
+/** Implementation of the {@link SpanExporter}. */
+public final class SpanExporterImpl extends SpanExporter {
+ private static final Logger logger = Logger.getLogger(ExportComponent.class.getName());
+
+ private final Worker worker;
+ private final Thread workerThread;
+
+ /**
+ * Constructs a {@code SpanExporterImpl} that exports the {@link SpanData} asynchronously.
+ *
+ * <p>Starts a separate thread that wakes up every {@code scheduleDelay} and exports any available
+ * spans data. If the number of buffered SpanData objects is greater than {@code bufferSize} then
+ * the thread wakes up sooner.
+ *
+ * @param bufferSize the size of the buffered span data.
+ * @param scheduleDelay the maximum delay.
+ */
+ static SpanExporterImpl create(int bufferSize, Duration scheduleDelay) {
+ // TODO(bdrutu): Consider to add a shutdown hook to not avoid dropping data.
+ Worker worker = new Worker(bufferSize, scheduleDelay);
+ return new SpanExporterImpl(worker);
+ }
+
+ /**
+ * Adds a Span to the exporting service.
+ *
+ * @param span the {@code Span} to be added.
+ */
+ public void addSpan(RecordEventsSpanImpl span) {
+ worker.addSpan(span);
+ }
+
+ @Override
+ public void registerHandler(String name, Handler handler) {
+ worker.registerHandler(name, handler);
+ }
+
+ @Override
+ public void unregisterHandler(String name) {
+ worker.unregisterHandler(name);
+ }
+
+ void flush() {
+ worker.flush();
+ }
+
+ void shutdown() {
+ flush();
+ workerThread.interrupt();
+ }
+
+ private SpanExporterImpl(Worker worker) {
+ this.workerThread =
+ new DaemonThreadFactory("ExportComponent.ServiceExporterThread").newThread(worker);
+ this.workerThread.start();
+ this.worker = worker;
+ }
+
+ @VisibleForTesting
+ Thread getServiceExporterThread() {
+ return workerThread;
+ }
+
+ // Worker in a thread that batches multiple span data and calls the registered services to export
+ // that data.
+ //
+ // The map of registered handlers is implemented using ConcurrentHashMap ensuring full
+ // concurrency of retrievals and adjustable expected concurrency for updates. Retrievals
+ // reflect the results of the most recently completed update operations held upon their onset.
+ //
+ // The list of batched data is protected by an explicit monitor object which ensures full
+ // concurrency.
+ private static final class Worker implements Runnable {
+ private final Object monitor = new Object();
+
+ @GuardedBy("monitor")
+ private final List<RecordEventsSpanImpl> spans;
+
+ private final Map<String, Handler> serviceHandlers = new ConcurrentHashMap<String, Handler>();
+ private final int bufferSize;
+ private final long scheduleDelayMillis;
+
+ // See SpanExporterImpl#addSpan.
+ private void addSpan(RecordEventsSpanImpl span) {
+ synchronized (monitor) {
+ this.spans.add(span);
+ if (spans.size() > bufferSize) {
+ monitor.notifyAll();
+ }
+ }
+ }
+
+ // See SpanExporter#registerHandler.
+ private void registerHandler(String name, Handler serviceHandler) {
+ serviceHandlers.put(name, serviceHandler);
+ }
+
+ // See SpanExporter#unregisterHandler.
+ private void unregisterHandler(String name) {
+ serviceHandlers.remove(name);
+ }
+
+ // Exports the list of SpanData to all the ServiceHandlers.
+ private void onBatchExport(List<SpanData> spanDataList) {
+ // From the java documentation of the ConcurrentHashMap#entrySet():
+ // The view's iterator is a "weakly consistent" iterator that will never throw
+ // ConcurrentModificationException, and guarantees to traverse elements as they existed
+ // upon construction of the iterator, and may (but is not guaranteed to) reflect any
+ // modifications subsequent to construction.
+ for (Map.Entry<String, Handler> it : serviceHandlers.entrySet()) {
+ // In case of any exception thrown by the service handlers continue to run.
+ try {
+ it.getValue().export(spanDataList);
+ } catch (Throwable e) {
+ logger.log(Level.WARNING, "Exception thrown by the service export " + it.getKey(), e);
+ }
+ }
+ }
+
+ private Worker(int bufferSize, Duration scheduleDelay) {
+ spans = new ArrayList<RecordEventsSpanImpl>(bufferSize);
+ this.bufferSize = bufferSize;
+ this.scheduleDelayMillis = scheduleDelay.toMillis();
+ }
+
+ // Returns an unmodifiable list of all buffered spans data to ensure that any registered
+ // service handler cannot modify the list.
+ private static List<SpanData> fromSpanImplToSpanData(List<RecordEventsSpanImpl> spans) {
+ List<SpanData> spanDatas = new ArrayList<SpanData>(spans.size());
+ for (RecordEventsSpanImpl span : spans) {
+ spanDatas.add(span.toSpanData());
+ }
+ return Collections.unmodifiableList(spanDatas);
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ // Copy all the batched spans in a separate list to release the monitor lock asap to
+ // avoid blocking the producer thread.
+ List<RecordEventsSpanImpl> spansCopy;
+ synchronized (monitor) {
+ if (spans.size() < bufferSize) {
+ do {
+ // In the case of a spurious wakeup we export only if we have at least one span in
+ // the batch. It is acceptable because batching is a best effort mechanism here.
+ try {
+ monitor.wait(scheduleDelayMillis);
+ } catch (InterruptedException ie) {
+ // Preserve the interruption status as per guidance and stop doing any work.
+ Thread.currentThread().interrupt();
+ return;
+ }
+ } while (spans.isEmpty());
+ }
+ spansCopy = new ArrayList<RecordEventsSpanImpl>(spans);
+ spans.clear();
+ }
+ // Execute the batch export outside the synchronized to not block all producers.
+ final List<SpanData> spanDataList = fromSpanImplToSpanData(spansCopy);
+ if (!spanDataList.isEmpty()) {
+ onBatchExport(spanDataList);
+ }
+ }
+ }
+
+ void flush() {
+ List<RecordEventsSpanImpl> spansCopy;
+ synchronized (monitor) {
+ spansCopy = new ArrayList<RecordEventsSpanImpl>(spans);
+ spans.clear();
+ }
+
+ final List<SpanData> spanDataList = fromSpanImplToSpanData(spansCopy);
+ if (!spanDataList.isEmpty()) {
+ onBatchExport(spanDataList);
+ }
+ }
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/internal/ConcurrentIntrusiveList.java b/impl_core/src/main/java/io/opencensus/implcore/trace/internal/ConcurrentIntrusiveList.java
new file mode 100644
index 00000000..22d8e41a
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/internal/ConcurrentIntrusiveList.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import io.opencensus.implcore.internal.CheckerFrameworkUtils;
+import io.opencensus.implcore.trace.internal.ConcurrentIntrusiveList.Element;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * An {@code ConcurrentIntrusiveList<T>} is a doubly-linked list where the link pointers are
+ * embedded in the elements. This makes insertion and removal into a known position constant time.
+ *
+ * <p>Elements must derive from the {@code Element<T extends Element<T>>} interface:
+ *
+ * <pre><code>
+ * class MyClass implements {@code Element<MyClass>} {
+ * private MyClass next = null;
+ * private MyClass prev = null;
+ *
+ * {@literal @}Override
+ * MyClass getNext() {
+ * return next;
+ * }
+ *
+ * {@literal @}Override
+ * void setNext(MyClass element) {
+ * next = element;
+ * }
+ *
+ * {@literal @}Override
+ * MyClass getPrev() {
+ * return prev;
+ * }
+ *
+ * {@literal @}Override
+ * void setPrev(MyClass element) {
+ * prev = element;
+ * }
+ * }
+ * </code></pre>
+ */
+@ThreadSafe
+public final class ConcurrentIntrusiveList<T extends Element<T>> {
+ private int size = 0;
+ @Nullable private T head = null;
+
+ public ConcurrentIntrusiveList() {}
+
+ /**
+ * Adds the given {@code element} to the list.
+ *
+ * @param element the element to add.
+ * @throws IllegalArgumentException if the element is already in a list.
+ */
+ public synchronized void addElement(T element) {
+ checkArgument(
+ element.getNext() == null && element.getPrev() == null && element != head,
+ "Element already in a list.");
+ size++;
+ if (head == null) {
+ head = element;
+ } else {
+ head.setPrev(element);
+ element.setNext(head);
+ head = element;
+ }
+ }
+
+ /**
+ * Removes the given {@code element} from the list.
+ *
+ * @param element the element to remove.
+ * @throws IllegalArgumentException if the element is not in the list.
+ */
+ public synchronized void removeElement(T element) {
+ checkArgument(
+ element.getNext() != null || element.getPrev() != null || element == head,
+ "Element not in the list.");
+ size--;
+ if (element.getPrev() == null) {
+ // This is the first element
+ head = element.getNext();
+ if (head != null) {
+ // If more than one element in the list.
+ head.setPrev(null);
+ element.setNext(null);
+ }
+ } else if (element.getNext() == null) {
+ // This is the last element, and there is at least another element because
+ // element.getPrev() != null.
+ CheckerFrameworkUtils.castNonNull(element.getPrev()).setNext(null);
+ element.setPrev(null);
+ } else {
+ CheckerFrameworkUtils.castNonNull(element.getPrev()).setNext(element.getNext());
+ CheckerFrameworkUtils.castNonNull(element.getNext()).setPrev(element.getPrev());
+ element.setNext(null);
+ element.setPrev(null);
+ }
+ }
+
+ /**
+ * Returns the number of elements in this list.
+ *
+ * @return the number of elements in this list.
+ */
+ public synchronized int size() {
+ return size;
+ }
+
+ /**
+ * Returns all the elements from this list.
+ *
+ * @return all the elements from this list.
+ */
+ public synchronized Collection<T> getAll() {
+ List<T> all = new ArrayList<T>(size);
+ for (T e = head; e != null; e = e.getNext()) {
+ all.add(e);
+ }
+ return all;
+ }
+
+ /**
+ * This is an interface that must be implemented by any element that uses {@link
+ * ConcurrentIntrusiveList}.
+ *
+ * @param <T> the element that will be used for the list.
+ */
+ public interface Element<T extends Element<T>> {
+
+ /**
+ * Returns a reference to the next element in the list.
+ *
+ * @return a reference to the next element in the list.
+ */
+ @Nullable
+ T getNext();
+
+ /**
+ * Sets the reference to the next element in the list.
+ *
+ * @param element the reference to the next element in the list.
+ */
+ void setNext(@Nullable T element);
+
+ /**
+ * Returns a reference to the previous element in the list.
+ *
+ * @return a reference to the previous element in the list.
+ */
+ @Nullable
+ T getPrev();
+
+ /**
+ * Sets the reference to the previous element in the list.
+ *
+ * @param element the reference to the previous element in the list.
+ */
+ void setPrev(@Nullable T element);
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/internal/RandomHandler.java b/impl_core/src/main/java/io/opencensus/implcore/trace/internal/RandomHandler.java
new file mode 100644
index 00000000..70be5a90
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/internal/RandomHandler.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace.internal;
+
+import java.security.SecureRandom;
+import java.util.Random;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Abstract class to access the current {@link Random}.
+ *
+ * <p>Implementation can have a per thread instance or a single global instance.
+ */
+@ThreadSafe
+public abstract class RandomHandler {
+ /**
+ * Returns the current {@link Random}.
+ *
+ * @return the current {@code Random}.
+ */
+ public abstract Random current();
+
+ /** Implementation of the {@link RandomHandler} using {@link SecureRandom}. */
+ @ThreadSafe
+ public static final class SecureRandomHandler extends RandomHandler {
+ private final Random random = new SecureRandom();
+
+ /** Constructs a new {@link SecureRandomHandler}. */
+ public SecureRandomHandler() {}
+
+ @Override
+ public Random current() {
+ return random;
+ }
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/B3Format.java b/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/B3Format.java
new file mode 100644
index 00000000..d928d93c
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/B3Format.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace.propagation;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.TraceId;
+import io.opencensus.trace.TraceOptions;
+import io.opencensus.trace.Tracestate;
+import io.opencensus.trace.propagation.SpanContextParseException;
+import io.opencensus.trace.propagation.TextFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/*>>>
+import org.checkerframework.checker.nullness.qual.NonNull;
+*/
+
+/**
+ * Implementation of the B3 propagation protocol. See <a
+ * href=https://github.com/openzipkin/b3-propagation>b3-propagation</a>.
+ */
+final class B3Format extends TextFormat {
+ private static final Tracestate TRACESTATE_DEFAULT = Tracestate.builder().build();
+ @VisibleForTesting static final String X_B3_TRACE_ID = "X-B3-TraceId";
+ @VisibleForTesting static final String X_B3_SPAN_ID = "X-B3-SpanId";
+ @VisibleForTesting static final String X_B3_PARENT_SPAN_ID = "X-B3-ParentSpanId";
+ @VisibleForTesting static final String X_B3_SAMPLED = "X-B3-Sampled";
+ @VisibleForTesting static final String X_B3_FLAGS = "X-B3-Flags";
+ private static final List<String> FIELDS =
+ Collections.unmodifiableList(
+ Arrays.asList(
+ X_B3_TRACE_ID, X_B3_SPAN_ID, X_B3_PARENT_SPAN_ID, X_B3_SAMPLED, X_B3_FLAGS));
+
+ // Used as the upper TraceId.SIZE hex characters of the traceID. B3-propagation used to send
+ // TraceId.SIZE hex characters (8-bytes traceId) in the past.
+ private static final String UPPER_TRACE_ID = "0000000000000000";
+ // Sampled value via the X_B3_SAMPLED header.
+ private static final String SAMPLED_VALUE = "1";
+ // "Debug" sampled value.
+ private static final String FLAGS_VALUE = "1";
+
+ @Override
+ public List<String> fields() {
+ return FIELDS;
+ }
+
+ @Override
+ public <C /*>>> extends @NonNull Object*/> void inject(
+ SpanContext spanContext, C carrier, Setter<C> setter) {
+ checkNotNull(spanContext, "spanContext");
+ checkNotNull(setter, "setter");
+ checkNotNull(carrier, "carrier");
+ setter.put(carrier, X_B3_TRACE_ID, spanContext.getTraceId().toLowerBase16());
+ setter.put(carrier, X_B3_SPAN_ID, spanContext.getSpanId().toLowerBase16());
+ if (spanContext.getTraceOptions().isSampled()) {
+ setter.put(carrier, X_B3_SAMPLED, SAMPLED_VALUE);
+ }
+ }
+
+ @Override
+ public <C /*>>> extends @NonNull Object*/> SpanContext extract(C carrier, Getter<C> getter)
+ throws SpanContextParseException {
+ checkNotNull(carrier, "carrier");
+ checkNotNull(getter, "getter");
+ try {
+ TraceId traceId;
+ String traceIdStr = getter.get(carrier, X_B3_TRACE_ID);
+ if (traceIdStr != null) {
+ if (traceIdStr.length() == TraceId.SIZE) {
+ // This is an 8-byte traceID.
+ traceIdStr = UPPER_TRACE_ID + traceIdStr;
+ }
+ traceId = TraceId.fromLowerBase16(traceIdStr);
+ } else {
+ throw new SpanContextParseException("Missing X_B3_TRACE_ID.");
+ }
+ SpanId spanId;
+ String spanIdStr = getter.get(carrier, X_B3_SPAN_ID);
+ if (spanIdStr != null) {
+ spanId = SpanId.fromLowerBase16(spanIdStr);
+ } else {
+ throw new SpanContextParseException("Missing X_B3_SPAN_ID.");
+ }
+ TraceOptions traceOptions = TraceOptions.DEFAULT;
+ if (SAMPLED_VALUE.equals(getter.get(carrier, X_B3_SAMPLED))
+ || FLAGS_VALUE.equals(getter.get(carrier, X_B3_FLAGS))) {
+ traceOptions = TraceOptions.builder().setIsSampled(true).build();
+ }
+ return SpanContext.create(traceId, spanId, traceOptions, TRACESTATE_DEFAULT);
+ } catch (IllegalArgumentException e) {
+ throw new SpanContextParseException("Invalid input.", e);
+ }
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/BinaryFormatImpl.java b/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/BinaryFormatImpl.java
new file mode 100644
index 00000000..233fbd31
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/BinaryFormatImpl.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace.propagation;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.TraceId;
+import io.opencensus.trace.TraceOptions;
+import io.opencensus.trace.Tracestate;
+import io.opencensus.trace.propagation.BinaryFormat;
+import io.opencensus.trace.propagation.SpanContextParseException;
+
+/**
+ * Implementation of the {@link BinaryFormat}.
+ *
+ * <p>BinaryFormat format:
+ *
+ * <ul>
+ * <li>Binary value: &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: &lt;field&gt;&lt;field&gt;
+ * <li>field_format: &lt;field_id&gt;&lt;field_format&gt;
+ * <li>Fields:
+ * <ul>
+ * <li>TraceId: (field_id = 0, len = 16, default = &#34;0000000000000000&#34;) -
+ * 16-byte array representing the trace_id.
+ * <li>SpanId: (field_id = 1, len = 8, default = &#34;00000000&#34;) - 8-byte array
+ * representing the span_id.
+ * <li>TraceOptions: (field_id = 2, len = 1, default = &#34;0&#34;) - 1-byte array
+ * representing the trace_options.
+ * </ul>
+ * <li>Fields MUST be encoded using the field id order (smaller to higher).
+ * <li>Valid value example:
+ * <ul>
+ * <li>{0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97,
+ * 98, 99, 100, 101, 102, 103, 104, 2, 1}
+ * <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 = {1};
+ * </ul>
+ * </ul>
+ * </ul>
+ */
+final class BinaryFormatImpl extends BinaryFormat {
+ private static final Tracestate TRACESTATE_DEFAULT = Tracestate.builder().build();
+ private static final byte VERSION_ID = 0;
+ private static final int VERSION_ID_OFFSET = 0;
+ // The version_id/field_id size in bytes.
+ private static final byte ID_SIZE = 1;
+ private static final byte TRACE_ID_FIELD_ID = 0;
+
+ // TODO: clarify if offsets are correct here. While the specification suggests you should stop
+ // parsing when you hit an unknown field, it does not suggest that fields must be declared in
+ // ID order. Rather it only groups by data type order, in this case Trace Context
+ // https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/BinaryEncoding.md#deserialization-rules
+ @VisibleForTesting static final int TRACE_ID_FIELD_ID_OFFSET = VERSION_ID_OFFSET + ID_SIZE;
+
+ private static final int TRACE_ID_OFFSET = TRACE_ID_FIELD_ID_OFFSET + ID_SIZE;
+ private static final byte SPAN_ID_FIELD_ID = 1;
+
+ @VisibleForTesting static final int SPAN_ID_FIELD_ID_OFFSET = TRACE_ID_OFFSET + TraceId.SIZE;
+
+ private static final int SPAN_ID_OFFSET = SPAN_ID_FIELD_ID_OFFSET + ID_SIZE;
+ private static final byte TRACE_OPTION_FIELD_ID = 2;
+
+ @VisibleForTesting static final int TRACE_OPTION_FIELD_ID_OFFSET = SPAN_ID_OFFSET + SpanId.SIZE;
+
+ private static final int TRACE_OPTIONS_OFFSET = TRACE_OPTION_FIELD_ID_OFFSET + ID_SIZE;
+ /** Version, Trace and Span IDs are required fields. */
+ private static final int REQUIRED_FORMAT_LENGTH = 3 * ID_SIZE + TraceId.SIZE + SpanId.SIZE;
+ /** Use {@link TraceOptions#DEFAULT} unless its optional field is present. */
+ private static final int ALL_FORMAT_LENGTH = REQUIRED_FORMAT_LENGTH + ID_SIZE + TraceOptions.SIZE;
+
+ @Override
+ public byte[] toByteArray(SpanContext spanContext) {
+ checkNotNull(spanContext, "spanContext");
+ byte[] bytes = new byte[ALL_FORMAT_LENGTH];
+ bytes[VERSION_ID_OFFSET] = VERSION_ID;
+ bytes[TRACE_ID_FIELD_ID_OFFSET] = TRACE_ID_FIELD_ID;
+ spanContext.getTraceId().copyBytesTo(bytes, TRACE_ID_OFFSET);
+ bytes[SPAN_ID_FIELD_ID_OFFSET] = SPAN_ID_FIELD_ID;
+ spanContext.getSpanId().copyBytesTo(bytes, SPAN_ID_OFFSET);
+ bytes[TRACE_OPTION_FIELD_ID_OFFSET] = TRACE_OPTION_FIELD_ID;
+ spanContext.getTraceOptions().copyBytesTo(bytes, TRACE_OPTIONS_OFFSET);
+ return bytes;
+ }
+
+ @Override
+ public SpanContext fromByteArray(byte[] bytes) throws SpanContextParseException {
+ checkNotNull(bytes, "bytes");
+ if (bytes.length == 0 || bytes[0] != VERSION_ID) {
+ throw new SpanContextParseException("Unsupported version.");
+ }
+ if (bytes.length < REQUIRED_FORMAT_LENGTH) {
+ throw new SpanContextParseException("Invalid input: truncated");
+ }
+ // TODO: the following logic assumes that fields are written in ID order. The spec does not say
+ // that. If it decides not to, this logic would need to be more like a loop
+ TraceId traceId;
+ SpanId spanId;
+ TraceOptions traceOptions = TraceOptions.DEFAULT;
+ int pos = 1;
+ if (bytes[pos] == TRACE_ID_FIELD_ID) {
+ traceId = TraceId.fromBytes(bytes, pos + ID_SIZE);
+ pos += ID_SIZE + TraceId.SIZE;
+ } else {
+ // TODO: update the spec to suggest that the trace ID is not actually optional
+ throw new SpanContextParseException("Invalid input: expected trace ID at offset " + pos);
+ }
+ if (bytes[pos] == SPAN_ID_FIELD_ID) {
+ spanId = SpanId.fromBytes(bytes, pos + ID_SIZE);
+ pos += ID_SIZE + SpanId.SIZE;
+ } else {
+ // TODO: update the spec to suggest that the span ID is not actually optional.
+ throw new SpanContextParseException("Invalid input: expected span ID at offset " + pos);
+ }
+ // Check to see if we are long enough to include an options field, and also that the next field
+ // is an options field. Per spec we simply stop parsing at first unknown field instead of
+ // failing.
+ if (bytes.length > pos && bytes[pos] == TRACE_OPTION_FIELD_ID) {
+ if (bytes.length < ALL_FORMAT_LENGTH) {
+ throw new SpanContextParseException("Invalid input: truncated");
+ }
+ traceOptions = TraceOptions.fromByte(bytes[pos + ID_SIZE]);
+ }
+ return SpanContext.create(traceId, spanId, traceOptions, TRACESTATE_DEFAULT);
+ }
+}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/PropagationComponentImpl.java b/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/PropagationComponentImpl.java
new file mode 100644
index 00000000..f608543d
--- /dev/null
+++ b/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/PropagationComponentImpl.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.implcore.trace.propagation;
+
+import io.opencensus.trace.propagation.BinaryFormat;
+import io.opencensus.trace.propagation.PropagationComponent;
+import io.opencensus.trace.propagation.TextFormat;
+
+/** Implementation of the {@link PropagationComponent}. */
+public class PropagationComponentImpl extends PropagationComponent {
+ private final BinaryFormat binaryFormat = new BinaryFormatImpl();
+ private final B3Format b3Format = new B3Format();
+
+ @Override
+ public BinaryFormat getBinaryFormat() {
+ return binaryFormat;
+ }
+
+ @Override
+ public TextFormat getB3Format() {
+ return b3Format;
+ }
+}