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