Home | History | Annotate | Download | only in trace
      1 /*
      2  * Copyright 2017, OpenCensus Authors
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *     http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package io.opencensus.implcore.trace;
     18 
     19 import static com.google.common.base.Preconditions.checkNotNull;
     20 import static com.google.common.base.Preconditions.checkState;
     21 
     22 import com.google.common.annotations.VisibleForTesting;
     23 import com.google.common.base.Preconditions;
     24 import com.google.common.collect.EvictingQueue;
     25 import io.opencensus.common.Clock;
     26 import io.opencensus.implcore.internal.CheckerFrameworkUtils;
     27 import io.opencensus.implcore.internal.TimestampConverter;
     28 import io.opencensus.implcore.trace.internal.ConcurrentIntrusiveList.Element;
     29 import io.opencensus.trace.Annotation;
     30 import io.opencensus.trace.AttributeValue;
     31 import io.opencensus.trace.EndSpanOptions;
     32 import io.opencensus.trace.Link;
     33 import io.opencensus.trace.Span;
     34 import io.opencensus.trace.SpanContext;
     35 import io.opencensus.trace.SpanId;
     36 import io.opencensus.trace.Status;
     37 import io.opencensus.trace.Tracer;
     38 import io.opencensus.trace.config.TraceParams;
     39 import io.opencensus.trace.export.SpanData;
     40 import io.opencensus.trace.export.SpanData.TimedEvent;
     41 import java.util.ArrayList;
     42 import java.util.Collections;
     43 import java.util.EnumSet;
     44 import java.util.LinkedHashMap;
     45 import java.util.List;
     46 import java.util.Map;
     47 import java.util.logging.Level;
     48 import java.util.logging.Logger;
     49 import javax.annotation.Nullable;
     50 import javax.annotation.concurrent.GuardedBy;
     51 import javax.annotation.concurrent.ThreadSafe;
     52 
     53 // TODO(hailongwen): remove the usage of `NetworkEvent` in the future.
     54 /** Implementation for the {@link Span} class that records trace events. */
     55 @ThreadSafe
     56 public final class RecordEventsSpanImpl extends Span implements Element<RecordEventsSpanImpl> {
     57   private static final Logger logger = Logger.getLogger(Tracer.class.getName());
     58 
     59   private static final EnumSet<Span.Options> RECORD_EVENTS_SPAN_OPTIONS =
     60       EnumSet.of(Span.Options.RECORD_EVENTS);
     61 
     62   // The parent SpanId of this span. Null if this is a root span.
     63   @Nullable private final SpanId parentSpanId;
     64   // True if the parent is on a different process.
     65   @Nullable private final Boolean hasRemoteParent;
     66   // Active trace params when the Span was created.
     67   private final TraceParams traceParams;
     68   // Handler called when the span starts and ends.
     69   private final StartEndHandler startEndHandler;
     70   // The displayed name of the span.
     71   private final String name;
     72   // The kind of the span.
     73   @Nullable private final Kind kind;
     74   // The clock used to get the time.
     75   private final Clock clock;
     76   // The time converter used to convert nano time to Timestamp. This is needed because Java has
     77   // millisecond granularity for Timestamp and tracing events are recorded more often.
     78   @Nullable private final TimestampConverter timestampConverter;
     79   // The start time of the span.
     80   private final long startNanoTime;
     81   // Set of recorded attributes. DO NOT CALL any other method that changes the ordering of events.
     82   @GuardedBy("this")
     83   @Nullable
     84   private AttributesWithCapacity attributes;
     85   // List of recorded annotations.
     86   @GuardedBy("this")
     87   @Nullable
     88   private TraceEvents<EventWithNanoTime<Annotation>> annotations;
     89   // List of recorded network events.
     90   @GuardedBy("this")
     91   @Nullable
     92   private TraceEvents<EventWithNanoTime<io.opencensus.trace.MessageEvent>> messageEvents;
     93   // List of recorded links to parent and child spans.
     94   @GuardedBy("this")
     95   @Nullable
     96   private TraceEvents<Link> links;
     97   // The status of the span.
     98   @GuardedBy("this")
     99   @Nullable
    100   private Status status;
    101   // The end time of the span.
    102   @GuardedBy("this")
    103   private long endNanoTime;
    104   // True if the span is ended.
    105   @GuardedBy("this")
    106   private boolean hasBeenEnded;
    107 
    108   @GuardedBy("this")
    109   private boolean sampleToLocalSpanStore;
    110 
    111   // Pointers for the ConcurrentIntrusiveList$Element. Guarded by the ConcurrentIntrusiveList.
    112   @Nullable private RecordEventsSpanImpl next = null;
    113   @Nullable private RecordEventsSpanImpl prev = null;
    114 
    115   /**
    116    * Creates and starts a span with the given configuration.
    117    *
    118    * @param context supplies the trace_id and span_id for the newly started span.
    119    * @param name the displayed name for the new span.
    120    * @param parentSpanId the span_id of the parent span, or null if the new span is a root span.
    121    * @param hasRemoteParent {@code true} if the parentContext is remote. {@code null} if this is a
    122    *     root span.
    123    * @param traceParams trace parameters like sampler and probability.
    124    * @param startEndHandler handler called when the span starts and ends.
    125    * @param timestampConverter null if the span is a root span or the parent is not sampled. If the
    126    *     parent is sampled, we should use the same converter to ensure ordering between tracing
    127    *     events.
    128    * @param clock the clock used to get the time.
    129    * @return a new and started span.
    130    */
    131   @VisibleForTesting
    132   public static RecordEventsSpanImpl startSpan(
    133       SpanContext context,
    134       String name,
    135       @Nullable Kind kind,
    136       @Nullable SpanId parentSpanId,
    137       @Nullable Boolean hasRemoteParent,
    138       TraceParams traceParams,
    139       StartEndHandler startEndHandler,
    140       @Nullable TimestampConverter timestampConverter,
    141       Clock clock) {
    142     RecordEventsSpanImpl span =
    143         new RecordEventsSpanImpl(
    144             context,
    145             name,
    146             kind,
    147             parentSpanId,
    148             hasRemoteParent,
    149             traceParams,
    150             startEndHandler,
    151             timestampConverter,
    152             clock);
    153     // Call onStart here instead of calling in the constructor to make sure the span is completely
    154     // initialized.
    155     startEndHandler.onStart(span);
    156     return span;
    157   }
    158 
    159   /**
    160    * Returns the name of the {@code Span}.
    161    *
    162    * @return the name of the {@code Span}.
    163    */
    164   public String getName() {
    165     return name;
    166   }
    167 
    168   /**
    169    * Returns the status of the {@code Span}. If not set defaults to {@link Status#OK}.
    170    *
    171    * @return the status of the {@code Span}.
    172    */
    173   public Status getStatus() {
    174     synchronized (this) {
    175       return getStatusWithDefault();
    176     }
    177   }
    178 
    179   /**
    180    * Returns the end nano time (see {@link System#nanoTime()}). If the current {@code Span} is not
    181    * ended then returns {@link Clock#nowNanos()}.
    182    *
    183    * @return the end nano time.
    184    */
    185   public long getEndNanoTime() {
    186     synchronized (this) {
    187       return hasBeenEnded ? endNanoTime : clock.nowNanos();
    188     }
    189   }
    190 
    191   /**
    192    * Returns the latency of the {@code Span} in nanos. If still active then returns now() - start
    193    * time.
    194    *
    195    * @return the latency of the {@code Span} in nanos.
    196    */
    197   public long getLatencyNs() {
    198     synchronized (this) {
    199       return hasBeenEnded ? endNanoTime - startNanoTime : clock.nowNanos() - startNanoTime;
    200     }
    201   }
    202 
    203   /**
    204    * Returns if the name of this {@code Span} must be register to the {@code SampledSpanStore}.
    205    *
    206    * @return if the name of this {@code Span} must be register to the {@code SampledSpanStore}.
    207    */
    208   public boolean getSampleToLocalSpanStore() {
    209     synchronized (this) {
    210       checkState(hasBeenEnded, "Running span does not have the SampleToLocalSpanStore set.");
    211       return sampleToLocalSpanStore;
    212     }
    213   }
    214 
    215   /**
    216    * Returns the kind of this {@code Span}.
    217    *
    218    * @return the kind of this {@code Span}.
    219    */
    220   @Nullable
    221   public Kind getKind() {
    222     return kind;
    223   }
    224 
    225   /**
    226    * Returns the {@code TimestampConverter} used by this {@code Span}.
    227    *
    228    * @return the {@code TimestampConverter} used by this {@code Span}.
    229    */
    230   @Nullable
    231   TimestampConverter getTimestampConverter() {
    232     return timestampConverter;
    233   }
    234 
    235   /**
    236    * Returns an immutable representation of all the data from this {@code Span}.
    237    *
    238    * @return an immutable representation of all the data from this {@code Span}.
    239    * @throws IllegalStateException if the Span doesn't have RECORD_EVENTS option.
    240    */
    241   public SpanData toSpanData() {
    242     synchronized (this) {
    243       SpanData.Attributes attributesSpanData =
    244           attributes == null
    245               ? SpanData.Attributes.create(Collections.<String, AttributeValue>emptyMap(), 0)
    246               : SpanData.Attributes.create(attributes, attributes.getNumberOfDroppedAttributes());
    247       SpanData.TimedEvents<Annotation> annotationsSpanData =
    248           createTimedEvents(getInitializedAnnotations(), timestampConverter);
    249       SpanData.TimedEvents<io.opencensus.trace.MessageEvent> messageEventsSpanData =
    250           createTimedEvents(getInitializedNetworkEvents(), timestampConverter);
    251       SpanData.Links linksSpanData =
    252           links == null
    253               ? SpanData.Links.create(Collections.<Link>emptyList(), 0)
    254               : SpanData.Links.create(
    255                   new ArrayList<Link>(links.events), links.getNumberOfDroppedEvents());
    256       return SpanData.create(
    257           getContext(),
    258           parentSpanId,
    259           hasRemoteParent,
    260           name,
    261           kind,
    262           CheckerFrameworkUtils.castNonNull(timestampConverter).convertNanoTime(startNanoTime),
    263           attributesSpanData,
    264           annotationsSpanData,
    265           messageEventsSpanData,
    266           linksSpanData,
    267           null, // Not supported yet.
    268           hasBeenEnded ? getStatusWithDefault() : null,
    269           hasBeenEnded
    270               ? CheckerFrameworkUtils.castNonNull(timestampConverter).convertNanoTime(endNanoTime)
    271               : null);
    272     }
    273   }
    274 
    275   @Override
    276   public void putAttribute(String key, AttributeValue value) {
    277     Preconditions.checkNotNull(key, "key");
    278     Preconditions.checkNotNull(value, "value");
    279     synchronized (this) {
    280       if (hasBeenEnded) {
    281         logger.log(Level.FINE, "Calling putAttributes() on an ended Span.");
    282         return;
    283       }
    284       getInitializedAttributes().putAttribute(key, value);
    285     }
    286   }
    287 
    288   @Override
    289   public void putAttributes(Map<String, AttributeValue> attributes) {
    290     Preconditions.checkNotNull(attributes, "attributes");
    291     synchronized (this) {
    292       if (hasBeenEnded) {
    293         logger.log(Level.FINE, "Calling putAttributes() on an ended Span.");
    294         return;
    295       }
    296       getInitializedAttributes().putAttributes(attributes);
    297     }
    298   }
    299 
    300   @Override
    301   public void addAnnotation(String description, Map<String, AttributeValue> attributes) {
    302     Preconditions.checkNotNull(description, "description");
    303     Preconditions.checkNotNull(attributes, "attribute");
    304     synchronized (this) {
    305       if (hasBeenEnded) {
    306         logger.log(Level.FINE, "Calling addAnnotation() on an ended Span.");
    307         return;
    308       }
    309       getInitializedAnnotations()
    310           .addEvent(
    311               new EventWithNanoTime<Annotation>(
    312                   clock.nowNanos(),
    313                   Annotation.fromDescriptionAndAttributes(description, attributes)));
    314     }
    315   }
    316 
    317   @Override
    318   public void addAnnotation(Annotation annotation) {
    319     Preconditions.checkNotNull(annotation, "annotation");
    320     synchronized (this) {
    321       if (hasBeenEnded) {
    322         logger.log(Level.FINE, "Calling addAnnotation() on an ended Span.");
    323         return;
    324       }
    325       getInitializedAnnotations()
    326           .addEvent(new EventWithNanoTime<Annotation>(clock.nowNanos(), annotation));
    327     }
    328   }
    329 
    330   @Override
    331   public void addMessageEvent(io.opencensus.trace.MessageEvent messageEvent) {
    332     Preconditions.checkNotNull(messageEvent, "messageEvent");
    333     synchronized (this) {
    334       if (hasBeenEnded) {
    335         logger.log(Level.FINE, "Calling addNetworkEvent() on an ended Span.");
    336         return;
    337       }
    338       getInitializedNetworkEvents()
    339           .addEvent(
    340               new EventWithNanoTime<io.opencensus.trace.MessageEvent>(
    341                   clock.nowNanos(), checkNotNull(messageEvent, "networkEvent")));
    342     }
    343   }
    344 
    345   @Override
    346   public void addLink(Link link) {
    347     Preconditions.checkNotNull(link, "link");
    348     synchronized (this) {
    349       if (hasBeenEnded) {
    350         logger.log(Level.FINE, "Calling addLink() on an ended Span.");
    351         return;
    352       }
    353       getInitializedLinks().addEvent(link);
    354     }
    355   }
    356 
    357   @Override
    358   public void setStatus(Status status) {
    359     Preconditions.checkNotNull(status, "status");
    360     synchronized (this) {
    361       if (hasBeenEnded) {
    362         logger.log(Level.FINE, "Calling setStatus() on an ended Span.");
    363         return;
    364       }
    365       this.status = status;
    366     }
    367   }
    368 
    369   @Override
    370   public void end(EndSpanOptions options) {
    371     Preconditions.checkNotNull(options, "options");
    372     synchronized (this) {
    373       if (hasBeenEnded) {
    374         logger.log(Level.FINE, "Calling end() on an ended Span.");
    375         return;
    376       }
    377       if (options.getStatus() != null) {
    378         status = options.getStatus();
    379       }
    380       sampleToLocalSpanStore = options.getSampleToLocalSpanStore();
    381       endNanoTime = clock.nowNanos();
    382       hasBeenEnded = true;
    383     }
    384     startEndHandler.onEnd(this);
    385   }
    386 
    387   @GuardedBy("this")
    388   private AttributesWithCapacity getInitializedAttributes() {
    389     if (attributes == null) {
    390       attributes = new AttributesWithCapacity(traceParams.getMaxNumberOfAttributes());
    391     }
    392     return attributes;
    393   }
    394 
    395   @GuardedBy("this")
    396   private TraceEvents<EventWithNanoTime<Annotation>> getInitializedAnnotations() {
    397     if (annotations == null) {
    398       annotations =
    399           new TraceEvents<EventWithNanoTime<Annotation>>(traceParams.getMaxNumberOfAnnotations());
    400     }
    401     return annotations;
    402   }
    403 
    404   @GuardedBy("this")
    405   private TraceEvents<EventWithNanoTime<io.opencensus.trace.MessageEvent>>
    406       getInitializedNetworkEvents() {
    407     if (messageEvents == null) {
    408       messageEvents =
    409           new TraceEvents<EventWithNanoTime<io.opencensus.trace.MessageEvent>>(
    410               traceParams.getMaxNumberOfMessageEvents());
    411     }
    412     return messageEvents;
    413   }
    414 
    415   @GuardedBy("this")
    416   private TraceEvents<Link> getInitializedLinks() {
    417     if (links == null) {
    418       links = new TraceEvents<Link>(traceParams.getMaxNumberOfLinks());
    419     }
    420     return links;
    421   }
    422 
    423   @GuardedBy("this")
    424   private Status getStatusWithDefault() {
    425     return status == null ? Status.OK : status;
    426   }
    427 
    428   private static <T> SpanData.TimedEvents<T> createTimedEvents(
    429       TraceEvents<EventWithNanoTime<T>> events, @Nullable TimestampConverter timestampConverter) {
    430     if (events == null) {
    431       return SpanData.TimedEvents.create(Collections.<TimedEvent<T>>emptyList(), 0);
    432     }
    433     List<TimedEvent<T>> eventsList = new ArrayList<TimedEvent<T>>(events.events.size());
    434     for (EventWithNanoTime<T> networkEvent : events.events) {
    435       eventsList.add(
    436           networkEvent.toSpanDataTimedEvent(CheckerFrameworkUtils.castNonNull(timestampConverter)));
    437     }
    438     return SpanData.TimedEvents.create(eventsList, events.getNumberOfDroppedEvents());
    439   }
    440 
    441   @Override
    442   @Nullable
    443   public RecordEventsSpanImpl getNext() {
    444     return next;
    445   }
    446 
    447   @Override
    448   public void setNext(@Nullable RecordEventsSpanImpl element) {
    449     next = element;
    450   }
    451 
    452   @Override
    453   @Nullable
    454   public RecordEventsSpanImpl getPrev() {
    455     return prev;
    456   }
    457 
    458   @Override
    459   public void setPrev(@Nullable RecordEventsSpanImpl element) {
    460     prev = element;
    461   }
    462 
    463   /**
    464    * Interface to handle the start and end operations for a {@link Span} only when the {@code Span}
    465    * has {@link Options#RECORD_EVENTS} option.
    466    *
    467    * <p>Implementation must avoid high overhead work in any of the methods because the code is
    468    * executed on the critical path.
    469    *
    470    * <p>One instance can be called by multiple threads in the same time, so the implementation must
    471    * be thread-safe.
    472    */
    473   public interface StartEndHandler {
    474     void onStart(RecordEventsSpanImpl span);
    475 
    476     void onEnd(RecordEventsSpanImpl span);
    477   }
    478 
    479   // A map implementation with a fixed capacity that drops events when the map gets full. Eviction
    480   // is based on the access order.
    481   private static final class AttributesWithCapacity extends LinkedHashMap<String, AttributeValue> {
    482     private final int capacity;
    483     private int totalRecordedAttributes = 0;
    484     // Here because -Werror complains about this: [serial] serializable class AttributesWithCapacity
    485     // has no definition of serialVersionUID. This class shouldn't be serialized.
    486     private static final long serialVersionUID = 42L;
    487 
    488     private AttributesWithCapacity(int capacity) {
    489       // Capacity of the map is capacity + 1 to avoid resizing because removeEldestEntry is invoked
    490       // by put and putAll after inserting a new entry into the map. The loadFactor is set to 1
    491       // to avoid resizing because. The accessOrder is set to true.
    492       super(capacity + 1, 1, /*accessOrder=*/ true);
    493       this.capacity = capacity;
    494     }
    495 
    496     // Users must call this method instead of put to keep count of the total number of entries
    497     // inserted.
    498     private void putAttribute(String key, AttributeValue value) {
    499       totalRecordedAttributes += 1;
    500       put(key, value);
    501     }
    502 
    503     // Users must call this method instead of putAll to keep count of the total number of entries
    504     // inserted.
    505     private void putAttributes(Map<String, AttributeValue> attributes) {
    506       totalRecordedAttributes += attributes.size();
    507       putAll(attributes);
    508     }
    509 
    510     private int getNumberOfDroppedAttributes() {
    511       return totalRecordedAttributes - size();
    512     }
    513 
    514     // It is called after each put or putAll call in order to determine if the eldest inserted
    515     // entry should be removed or not.
    516     @Override
    517     protected boolean removeEldestEntry(Map.Entry<String, AttributeValue> eldest) {
    518       return size() > this.capacity;
    519     }
    520   }
    521 
    522   private static final class TraceEvents<T> {
    523     private int totalRecordedEvents = 0;
    524     private final EvictingQueue<T> events;
    525 
    526     private int getNumberOfDroppedEvents() {
    527       return totalRecordedEvents - events.size();
    528     }
    529 
    530     TraceEvents(int maxNumEvents) {
    531       events = EvictingQueue.create(maxNumEvents);
    532     }
    533 
    534     void addEvent(T event) {
    535       totalRecordedEvents++;
    536       events.add(event);
    537     }
    538   }
    539 
    540   // Timed event that uses nanoTime to represent the Timestamp.
    541   private static final class EventWithNanoTime<T> {
    542     private final long nanoTime;
    543     private final T event;
    544 
    545     private EventWithNanoTime(long nanoTime, T event) {
    546       this.nanoTime = nanoTime;
    547       this.event = event;
    548     }
    549 
    550     private TimedEvent<T> toSpanDataTimedEvent(TimestampConverter timestampConverter) {
    551       return TimedEvent.create(timestampConverter.convertNanoTime(nanoTime), event);
    552     }
    553   }
    554 
    555   private RecordEventsSpanImpl(
    556       SpanContext context,
    557       String name,
    558       @Nullable Kind kind,
    559       @Nullable SpanId parentSpanId,
    560       @Nullable Boolean hasRemoteParent,
    561       TraceParams traceParams,
    562       StartEndHandler startEndHandler,
    563       @Nullable TimestampConverter timestampConverter,
    564       Clock clock) {
    565     super(context, RECORD_EVENTS_SPAN_OPTIONS);
    566     this.parentSpanId = parentSpanId;
    567     this.hasRemoteParent = hasRemoteParent;
    568     this.name = name;
    569     this.kind = kind;
    570     this.traceParams = traceParams;
    571     this.startEndHandler = startEndHandler;
    572     this.clock = clock;
    573     this.hasBeenEnded = false;
    574     this.sampleToLocalSpanStore = false;
    575     this.timestampConverter =
    576         timestampConverter != null ? timestampConverter : TimestampConverter.now(clock);
    577     startNanoTime = clock.nowNanos();
    578   }
    579 }
    580