• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.TimestampConverter;
27 import io.opencensus.implcore.trace.internal.ConcurrentIntrusiveList.Element;
28 import io.opencensus.trace.Annotation;
29 import io.opencensus.trace.AttributeValue;
30 import io.opencensus.trace.EndSpanOptions;
31 import io.opencensus.trace.Link;
32 import io.opencensus.trace.Span;
33 import io.opencensus.trace.SpanContext;
34 import io.opencensus.trace.SpanId;
35 import io.opencensus.trace.Status;
36 import io.opencensus.trace.Tracer;
37 import io.opencensus.trace.config.TraceParams;
38 import io.opencensus.trace.export.SpanData;
39 import io.opencensus.trace.export.SpanData.TimedEvent;
40 import java.util.ArrayList;
41 import java.util.Collections;
42 import java.util.EnumSet;
43 import java.util.LinkedHashMap;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.logging.Level;
47 import java.util.logging.Logger;
48 import javax.annotation.Nullable;
49 import javax.annotation.concurrent.GuardedBy;
50 import javax.annotation.concurrent.ThreadSafe;
51 
52 // TODO(hailongwen): remove the usage of `NetworkEvent` in the future.
53 /** Implementation for the {@link Span} class that records trace events. */
54 @ThreadSafe
55 public final class RecordEventsSpanImpl extends Span implements Element<RecordEventsSpanImpl> {
56   private static final Logger logger = Logger.getLogger(Tracer.class.getName());
57 
58   private static final EnumSet<Span.Options> RECORD_EVENTS_SPAN_OPTIONS =
59       EnumSet.of(Span.Options.RECORD_EVENTS);
60 
61   // The parent SpanId of this span. Null if this is a root span.
62   @Nullable private final SpanId parentSpanId;
63   // True if the parent is on a different process.
64   @Nullable private final Boolean hasRemoteParent;
65   // Active trace params when the Span was created.
66   private final TraceParams traceParams;
67   // Handler called when the span starts and ends.
68   private final StartEndHandler startEndHandler;
69   // The displayed name of the span.
70   private final String name;
71   // The kind of the span.
72   @Nullable private final Kind kind;
73   // The clock used to get the time.
74   private final Clock clock;
75   // The time converter used to convert nano time to Timestamp. This is needed because Java has
76   // millisecond granularity for Timestamp and tracing events are recorded more often.
77   private final TimestampConverter timestampConverter;
78   // The start time of the span.
79   private final long startNanoTime;
80   // Set of recorded attributes. DO NOT CALL any other method that changes the ordering of events.
81   @GuardedBy("this")
82   @Nullable
83   private AttributesWithCapacity attributes;
84   // List of recorded annotations.
85   @GuardedBy("this")
86   @Nullable
87   private TraceEvents<EventWithNanoTime<Annotation>> annotations;
88   // List of recorded network events.
89   @GuardedBy("this")
90   @Nullable
91   private TraceEvents<EventWithNanoTime<io.opencensus.trace.MessageEvent>> messageEvents;
92   // List of recorded links to parent and child spans.
93   @GuardedBy("this")
94   @Nullable
95   private TraceEvents<Link> links;
96   // The number of children.
97   @GuardedBy("this")
98   private int numberOfChildren;
99   // The status of the span.
100   @GuardedBy("this")
101   @Nullable
102   private Status status;
103   // The end time of the span.
104   @GuardedBy("this")
105   private long endNanoTime;
106   // True if the span is ended.
107   @GuardedBy("this")
108   private boolean hasBeenEnded;
109 
110   @GuardedBy("this")
111   private boolean sampleToLocalSpanStore;
112 
113   // Pointers for the ConcurrentIntrusiveList$Element. Guarded by the ConcurrentIntrusiveList.
114   @Nullable private RecordEventsSpanImpl next = null;
115   @Nullable private RecordEventsSpanImpl prev = null;
116 
117   /**
118    * Creates and starts a span with the given configuration.
119    *
120    * @param context supplies the trace_id and span_id for the newly started span.
121    * @param name the displayed name for the new span.
122    * @param parentSpanId the span_id of the parent span, or null if the new span is a root span.
123    * @param hasRemoteParent {@code true} if the parentContext is remote. {@code null} if this is a
124    *     root span.
125    * @param traceParams trace parameters like sampler and probability.
126    * @param startEndHandler handler called when the span starts and ends.
127    * @param timestampConverter null if the span is a root span or the parent is not sampled. If the
128    *     parent is sampled, we should use the same converter to ensure ordering between tracing
129    *     events.
130    * @param clock the clock used to get the time.
131    * @return a new and started span.
132    */
133   @VisibleForTesting
startSpan( SpanContext context, String name, @Nullable Kind kind, @Nullable SpanId parentSpanId, @Nullable Boolean hasRemoteParent, TraceParams traceParams, StartEndHandler startEndHandler, @Nullable TimestampConverter timestampConverter, Clock clock)134   public static RecordEventsSpanImpl startSpan(
135       SpanContext context,
136       String name,
137       @Nullable Kind kind,
138       @Nullable SpanId parentSpanId,
139       @Nullable Boolean hasRemoteParent,
140       TraceParams traceParams,
141       StartEndHandler startEndHandler,
142       @Nullable TimestampConverter timestampConverter,
143       Clock clock) {
144     RecordEventsSpanImpl span =
145         new RecordEventsSpanImpl(
146             context,
147             name,
148             kind,
149             parentSpanId,
150             hasRemoteParent,
151             traceParams,
152             startEndHandler,
153             timestampConverter,
154             clock);
155     // Call onStart here instead of calling in the constructor to make sure the span is completely
156     // initialized.
157     startEndHandler.onStart(span);
158     return span;
159   }
160 
161   /**
162    * Returns the name of the {@code Span}.
163    *
164    * @return the name of the {@code Span}.
165    */
getName()166   public String getName() {
167     return name;
168   }
169 
170   /**
171    * Returns the status of the {@code Span}. If not set defaults to {@link Status#OK}.
172    *
173    * @return the status of the {@code Span}.
174    */
getStatus()175   public Status getStatus() {
176     synchronized (this) {
177       return getStatusWithDefault();
178     }
179   }
180 
181   /**
182    * Returns the end nano time (see {@link System#nanoTime()}). If the current {@code Span} is not
183    * ended then returns {@link Clock#nowNanos()}.
184    *
185    * @return the end nano time.
186    */
getEndNanoTime()187   public long getEndNanoTime() {
188     synchronized (this) {
189       return hasBeenEnded ? endNanoTime : clock.nowNanos();
190     }
191   }
192 
193   /**
194    * Returns the latency of the {@code Span} in nanos. If still active then returns now() - start
195    * time.
196    *
197    * @return the latency of the {@code Span} in nanos.
198    */
getLatencyNs()199   public long getLatencyNs() {
200     synchronized (this) {
201       return hasBeenEnded ? endNanoTime - startNanoTime : clock.nowNanos() - startNanoTime;
202     }
203   }
204 
205   /**
206    * Returns if the name of this {@code Span} must be register to the {@code SampledSpanStore}.
207    *
208    * @return if the name of this {@code Span} must be register to the {@code SampledSpanStore}.
209    */
getSampleToLocalSpanStore()210   public boolean getSampleToLocalSpanStore() {
211     synchronized (this) {
212       checkState(hasBeenEnded, "Running span does not have the SampleToLocalSpanStore set.");
213       return sampleToLocalSpanStore;
214     }
215   }
216 
217   /**
218    * Returns the kind of this {@code Span}.
219    *
220    * @return the kind of this {@code Span}.
221    */
222   @Nullable
getKind()223   public Kind getKind() {
224     return kind;
225   }
226 
227   /**
228    * Returns the {@code TimestampConverter} used by this {@code Span}.
229    *
230    * @return the {@code TimestampConverter} used by this {@code Span}.
231    */
232   @Nullable
getTimestampConverter()233   TimestampConverter getTimestampConverter() {
234     return timestampConverter;
235   }
236 
237   /**
238    * Returns an immutable representation of all the data from this {@code Span}.
239    *
240    * @return an immutable representation of all the data from this {@code Span}.
241    * @throws IllegalStateException if the Span doesn't have RECORD_EVENTS option.
242    */
toSpanData()243   public SpanData toSpanData() {
244     synchronized (this) {
245       SpanData.Attributes attributesSpanData =
246           attributes == null
247               ? SpanData.Attributes.create(Collections.<String, AttributeValue>emptyMap(), 0)
248               : SpanData.Attributes.create(attributes, attributes.getNumberOfDroppedAttributes());
249       SpanData.TimedEvents<Annotation> annotationsSpanData =
250           createTimedEvents(getInitializedAnnotations(), timestampConverter);
251       SpanData.TimedEvents<io.opencensus.trace.MessageEvent> messageEventsSpanData =
252           createTimedEvents(getInitializedNetworkEvents(), timestampConverter);
253       SpanData.Links linksSpanData =
254           links == null
255               ? SpanData.Links.create(Collections.<Link>emptyList(), 0)
256               : SpanData.Links.create(
257                   new ArrayList<Link>(links.events), links.getNumberOfDroppedEvents());
258       return SpanData.create(
259           getContext(),
260           parentSpanId,
261           hasRemoteParent,
262           name,
263           kind,
264           timestampConverter.convertNanoTime(startNanoTime),
265           attributesSpanData,
266           annotationsSpanData,
267           messageEventsSpanData,
268           linksSpanData,
269           numberOfChildren,
270           hasBeenEnded ? getStatusWithDefault() : null,
271           hasBeenEnded ? timestampConverter.convertNanoTime(endNanoTime) : 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 
addChild()387   void addChild() {
388     synchronized (this) {
389       if (hasBeenEnded) {
390         logger.log(Level.FINE, "Calling end() on an ended Span.");
391         return;
392       }
393       numberOfChildren++;
394     }
395   }
396 
397   @GuardedBy("this")
getInitializedAttributes()398   private AttributesWithCapacity getInitializedAttributes() {
399     if (attributes == null) {
400       attributes = new AttributesWithCapacity(traceParams.getMaxNumberOfAttributes());
401     }
402     return attributes;
403   }
404 
405   @GuardedBy("this")
getInitializedAnnotations()406   private TraceEvents<EventWithNanoTime<Annotation>> getInitializedAnnotations() {
407     if (annotations == null) {
408       annotations =
409           new TraceEvents<EventWithNanoTime<Annotation>>(traceParams.getMaxNumberOfAnnotations());
410     }
411     return annotations;
412   }
413 
414   @GuardedBy("this")
415   private TraceEvents<EventWithNanoTime<io.opencensus.trace.MessageEvent>>
getInitializedNetworkEvents()416       getInitializedNetworkEvents() {
417     if (messageEvents == null) {
418       messageEvents =
419           new TraceEvents<EventWithNanoTime<io.opencensus.trace.MessageEvent>>(
420               traceParams.getMaxNumberOfMessageEvents());
421     }
422     return messageEvents;
423   }
424 
425   @GuardedBy("this")
getInitializedLinks()426   private TraceEvents<Link> getInitializedLinks() {
427     if (links == null) {
428       links = new TraceEvents<Link>(traceParams.getMaxNumberOfLinks());
429     }
430     return links;
431   }
432 
433   @GuardedBy("this")
getStatusWithDefault()434   private Status getStatusWithDefault() {
435     return status == null ? Status.OK : status;
436   }
437 
createTimedEvents( TraceEvents<EventWithNanoTime<T>> events, TimestampConverter timestampConverter)438   private static <T> SpanData.TimedEvents<T> createTimedEvents(
439       TraceEvents<EventWithNanoTime<T>> events, TimestampConverter timestampConverter) {
440     if (events == null) {
441       return SpanData.TimedEvents.create(Collections.<TimedEvent<T>>emptyList(), 0);
442     }
443     List<TimedEvent<T>> eventsList = new ArrayList<TimedEvent<T>>(events.events.size());
444     for (EventWithNanoTime<T> networkEvent : events.events) {
445       eventsList.add(networkEvent.toSpanDataTimedEvent(timestampConverter));
446     }
447     return SpanData.TimedEvents.create(eventsList, events.getNumberOfDroppedEvents());
448   }
449 
450   @Override
451   @Nullable
getNext()452   public RecordEventsSpanImpl getNext() {
453     return next;
454   }
455 
456   @Override
setNext(@ullable RecordEventsSpanImpl element)457   public void setNext(@Nullable RecordEventsSpanImpl element) {
458     next = element;
459   }
460 
461   @Override
462   @Nullable
getPrev()463   public RecordEventsSpanImpl getPrev() {
464     return prev;
465   }
466 
467   @Override
setPrev(@ullable RecordEventsSpanImpl element)468   public void setPrev(@Nullable RecordEventsSpanImpl element) {
469     prev = element;
470   }
471 
472   /**
473    * Interface to handle the start and end operations for a {@link Span} only when the {@code Span}
474    * has {@link Options#RECORD_EVENTS} option.
475    *
476    * <p>Implementation must avoid high overhead work in any of the methods because the code is
477    * executed on the critical path.
478    *
479    * <p>One instance can be called by multiple threads in the same time, so the implementation must
480    * be thread-safe.
481    */
482   public interface StartEndHandler {
onStart(RecordEventsSpanImpl span)483     void onStart(RecordEventsSpanImpl span);
484 
onEnd(RecordEventsSpanImpl span)485     void onEnd(RecordEventsSpanImpl span);
486   }
487 
488   // A map implementation with a fixed capacity that drops events when the map gets full. Eviction
489   // is based on the access order.
490   private static final class AttributesWithCapacity extends LinkedHashMap<String, AttributeValue> {
491     private final int capacity;
492     private int totalRecordedAttributes = 0;
493     // Here because -Werror complains about this: [serial] serializable class AttributesWithCapacity
494     // has no definition of serialVersionUID. This class shouldn't be serialized.
495     private static final long serialVersionUID = 42L;
496 
AttributesWithCapacity(int capacity)497     private AttributesWithCapacity(int capacity) {
498       // Capacity of the map is capacity + 1 to avoid resizing because removeEldestEntry is invoked
499       // by put and putAll after inserting a new entry into the map. The loadFactor is set to 1
500       // to avoid resizing because. The accessOrder is set to true.
501       super(capacity + 1, 1, /*accessOrder=*/ true);
502       this.capacity = capacity;
503     }
504 
505     // Users must call this method instead of put to keep count of the total number of entries
506     // inserted.
putAttribute(String key, AttributeValue value)507     private void putAttribute(String key, AttributeValue value) {
508       totalRecordedAttributes += 1;
509       put(key, value);
510     }
511 
512     // Users must call this method instead of putAll to keep count of the total number of entries
513     // inserted.
putAttributes(Map<String, AttributeValue> attributes)514     private void putAttributes(Map<String, AttributeValue> attributes) {
515       totalRecordedAttributes += attributes.size();
516       putAll(attributes);
517     }
518 
getNumberOfDroppedAttributes()519     private int getNumberOfDroppedAttributes() {
520       return totalRecordedAttributes - size();
521     }
522 
523     // It is called after each put or putAll call in order to determine if the eldest inserted
524     // entry should be removed or not.
525     @Override
removeEldestEntry(Map.Entry<String, AttributeValue> eldest)526     protected boolean removeEldestEntry(Map.Entry<String, AttributeValue> eldest) {
527       return size() > this.capacity;
528     }
529   }
530 
531   private static final class TraceEvents<T> {
532     private int totalRecordedEvents = 0;
533     private final EvictingQueue<T> events;
534 
getNumberOfDroppedEvents()535     private int getNumberOfDroppedEvents() {
536       return totalRecordedEvents - events.size();
537     }
538 
TraceEvents(int maxNumEvents)539     TraceEvents(int maxNumEvents) {
540       events = EvictingQueue.create(maxNumEvents);
541     }
542 
addEvent(T event)543     void addEvent(T event) {
544       totalRecordedEvents++;
545       events.add(event);
546     }
547   }
548 
549   // Timed event that uses nanoTime to represent the Timestamp.
550   private static final class EventWithNanoTime<T> {
551     private final long nanoTime;
552     private final T event;
553 
EventWithNanoTime(long nanoTime, T event)554     private EventWithNanoTime(long nanoTime, T event) {
555       this.nanoTime = nanoTime;
556       this.event = event;
557     }
558 
toSpanDataTimedEvent(TimestampConverter timestampConverter)559     private TimedEvent<T> toSpanDataTimedEvent(TimestampConverter timestampConverter) {
560       return TimedEvent.create(timestampConverter.convertNanoTime(nanoTime), event);
561     }
562   }
563 
RecordEventsSpanImpl( SpanContext context, String name, @Nullable Kind kind, @Nullable SpanId parentSpanId, @Nullable Boolean hasRemoteParent, TraceParams traceParams, StartEndHandler startEndHandler, @Nullable TimestampConverter timestampConverter, Clock clock)564   private RecordEventsSpanImpl(
565       SpanContext context,
566       String name,
567       @Nullable Kind kind,
568       @Nullable SpanId parentSpanId,
569       @Nullable Boolean hasRemoteParent,
570       TraceParams traceParams,
571       StartEndHandler startEndHandler,
572       @Nullable TimestampConverter timestampConverter,
573       Clock clock) {
574     super(context, RECORD_EVENTS_SPAN_OPTIONS);
575     this.parentSpanId = parentSpanId;
576     this.hasRemoteParent = hasRemoteParent;
577     this.name = name;
578     this.kind = kind;
579     this.traceParams = traceParams;
580     this.startEndHandler = startEndHandler;
581     this.clock = clock;
582     this.hasBeenEnded = false;
583     this.sampleToLocalSpanStore = false;
584     this.numberOfChildren = 0;
585     this.timestampConverter =
586         timestampConverter != null ? timestampConverter : TimestampConverter.now(clock);
587     startNanoTime = clock.nowNanos();
588   }
589 }
590