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
startSpan( SpanContext context, String name, @Nullable Kind kind, @Nullable SpanId parentSpanId, @Nullable Boolean hasRemoteParent, TraceParams traceParams, StartEndHandler startEndHandler, @Nullable TimestampConverter timestampConverter, Clock clock)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    */
getName()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    */
getStatus()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    */
getEndNanoTime()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    */
getLatencyNs()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    */
getSampleToLocalSpanStore()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
getKind()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
getTimestampConverter()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    */
toSpanData()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
putAttribute(String key, AttributeValue value)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
putAttributes(Map<String, AttributeValue> attributes)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
addAnnotation(String description, Map<String, AttributeValue> attributes)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
addAnnotation(Annotation annotation)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
addMessageEvent(io.opencensus.trace.MessageEvent messageEvent)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
addLink(Link link)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
setStatus(Status status)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
end(EndSpanOptions options)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")
getInitializedAttributes()388   private AttributesWithCapacity getInitializedAttributes() {
389     if (attributes == null) {
390       attributes = new AttributesWithCapacity(traceParams.getMaxNumberOfAttributes());
391     }
392     return attributes;
393   }
394 
395   @GuardedBy("this")
getInitializedAnnotations()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>>
getInitializedNetworkEvents()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")
getInitializedLinks()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")
getStatusWithDefault()424   private Status getStatusWithDefault() {
425     return status == null ? Status.OK : status;
426   }
427 
createTimedEvents( TraceEvents<EventWithNanoTime<T>> events, @Nullable TimestampConverter timestampConverter)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
getNext()443   public RecordEventsSpanImpl getNext() {
444     return next;
445   }
446 
447   @Override
setNext(@ullable RecordEventsSpanImpl element)448   public void setNext(@Nullable RecordEventsSpanImpl element) {
449     next = element;
450   }
451 
452   @Override
453   @Nullable
getPrev()454   public RecordEventsSpanImpl getPrev() {
455     return prev;
456   }
457 
458   @Override
setPrev(@ullable RecordEventsSpanImpl element)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 {
onStart(RecordEventsSpanImpl span)474     void onStart(RecordEventsSpanImpl span);
475 
onEnd(RecordEventsSpanImpl span)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 
AttributesWithCapacity(int capacity)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.
putAttribute(String key, AttributeValue value)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.
putAttributes(Map<String, AttributeValue> attributes)505     private void putAttributes(Map<String, AttributeValue> attributes) {
506       totalRecordedAttributes += attributes.size();
507       putAll(attributes);
508     }
509 
getNumberOfDroppedAttributes()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
removeEldestEntry(Map.Entry<String, AttributeValue> eldest)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 
getNumberOfDroppedEvents()526     private int getNumberOfDroppedEvents() {
527       return totalRecordedEvents - events.size();
528     }
529 
TraceEvents(int maxNumEvents)530     TraceEvents(int maxNumEvents) {
531       events = EvictingQueue.create(maxNumEvents);
532     }
533 
addEvent(T event)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 
EventWithNanoTime(long nanoTime, T event)545     private EventWithNanoTime(long nanoTime, T event) {
546       this.nanoTime = nanoTime;
547       this.event = event;
548     }
549 
toSpanDataTimedEvent(TimestampConverter timestampConverter)550     private TimedEvent<T> toSpanDataTimedEvent(TimestampConverter timestampConverter) {
551       return TimedEvent.create(timestampConverter.convertNanoTime(nanoTime), event);
552     }
553   }
554 
RecordEventsSpanImpl( SpanContext context, String name, @Nullable Kind kind, @Nullable SpanId parentSpanId, @Nullable Boolean hasRemoteParent, TraceParams traceParams, StartEndHandler startEndHandler, @Nullable TimestampConverter timestampConverter, Clock clock)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