aboutsummaryrefslogtreecommitdiff
path: root/impl_core/src/main/java/io/opencensus/implcore/trace/RecordEventsSpanImpl.java
diff options
context:
space:
mode:
Diffstat (limited to 'impl_core/src/main/java/io/opencensus/implcore/trace/RecordEventsSpanImpl.java')
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/trace/RecordEventsSpanImpl.java579
1 files changed, 579 insertions, 0 deletions
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();
+ }
+}