github.com/waldiirawan/apm-agent-go/v2@v2.2.2/span.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package apm // import "github.com/waldiirawan/apm-agent-go/v2" 19 20 import ( 21 cryptorand "crypto/rand" 22 "encoding/binary" 23 "strings" 24 "sync" 25 "sync/atomic" 26 "time" 27 28 "github.com/waldiirawan/apm-agent-go/v2/stacktrace" 29 ) 30 31 // droppedSpanDataPool holds *SpanData which are used when the span is created 32 // for a nil or non-sampled trace context, without a transaction reference. 33 // 34 // Spans started with a non-nil transaction, even if it is non-sampled, are 35 // always created with the transaction's tracer span pool. 36 var droppedSpanDataPool sync.Pool 37 38 // StartSpan starts and returns a new Span within the transaction, 39 // with the specified name, type, and optional parent span, and 40 // with the start time set to the current time. 41 // 42 // StartSpan always returns a non-nil Span, with a non-nil SpanData 43 // field. Its End method must be called when the span completes. 44 // 45 // If the span type contains two dots, they are assumed to separate 46 // the span type, subtype, and action; a single dot separates span 47 // type and subtype, and the action will not be set. 48 // 49 // StartSpan is equivalent to calling StartSpanOptions with 50 // SpanOptions.Parent set to the trace context of parent if 51 // parent is non-nil. 52 func (tx *Transaction) StartSpan(name, spanType string, parent *Span) *Span { 53 return tx.StartSpanOptions(name, spanType, SpanOptions{ 54 parent: parent, 55 }) 56 } 57 58 // StartExitSpan starts and returns a new Span within the transaction, 59 // with the specified name, type, and optional parent span, and 60 // with the start time set to the current time. 61 // 62 // StartExitSpan always returns a non-nil Span, with a non-nil SpanData 63 // field. Its End method must be called when the span completes. 64 // 65 // If the span type contains two dots, they are assumed to separate 66 // the span type, subtype, and action; a single dot separates span 67 // type and subtype, and the action will not be set. 68 // 69 // StartExitSpan is equivalent to calling StartSpanOptions with 70 // SpanOptions.Parent set to the trace context of parent if 71 // parent is non-nil and the span being marked as an exit span. 72 func (tx *Transaction) StartExitSpan(name, spanType string, parent *Span) *Span { 73 return tx.StartSpanOptions(name, spanType, SpanOptions{ 74 parent: parent, 75 ExitSpan: true, 76 }) 77 } 78 79 // StartSpanOptions starts and returns a new Span within the transaction, 80 // with the specified name, type, and options. 81 // 82 // StartSpan always returns a non-nil Span. Its End method must be called 83 // when the span completes. 84 // 85 // If the span type contains two dots, they are assumed to separate the 86 // span type, subtype, and action; a single dot separates span type and 87 // subtype, and the action will not be set. 88 func (tx *Transaction) StartSpanOptions(name, spanType string, opts SpanOptions) *Span { 89 if tx == nil { 90 return newDroppedSpan() 91 } 92 93 if opts.Parent == (TraceContext{}) { 94 if opts.parent != nil { 95 opts.Parent = opts.parent.TraceContext() 96 } else { 97 opts.Parent = tx.traceContext 98 } 99 } 100 transactionID := tx.traceContext.Span 101 102 // Lock the parent first to avoid deadlocks in breakdown metrics calculation. 103 if opts.parent != nil { 104 opts.parent.mu.Lock() 105 defer opts.parent.mu.Unlock() 106 } 107 108 // Prevent tx from being ended while we're starting a span. 109 tx.mu.RLock() 110 defer tx.mu.RUnlock() 111 if tx.ended() { 112 return tx.tracer.StartSpan(name, spanType, transactionID, opts) 113 } 114 115 // Calculate the span time relative to the transaction timestamp so 116 // that wall-clock adjustments occurring after the transaction start 117 // don't affect the span timestamp. 118 if opts.Start.IsZero() { 119 opts.Start = tx.timestamp.Add(time.Since(tx.timestamp)) 120 } else { 121 opts.Start = tx.timestamp.Add(opts.Start.Sub(tx.timestamp)) 122 } 123 span := tx.tracer.startSpan(name, spanType, transactionID, opts) 124 span.tx = tx 125 span.parent = opts.parent 126 if opts.ExitSpan { 127 span.exit = true 128 } 129 130 // Guard access to spansCreated, spansDropped, rand, and childrenTimer. 131 tx.TransactionData.mu.Lock() 132 defer tx.TransactionData.mu.Unlock() 133 134 notRecorded := !span.traceContext.Options.Recorded() 135 exceedsMaxSpans := tx.maxSpans >= 0 && tx.spansCreated >= tx.maxSpans 136 // Drop span when it is not recorded. 137 if span.dropWhen(notRecorded) { 138 // nothing to do here since it isn't recorded. 139 } else if span.dropWhen(exceedsMaxSpans) { 140 tx.spansDropped++ 141 } else { 142 if opts.SpanID.Validate() == nil { 143 span.traceContext.Span = opts.SpanID 144 } else { 145 binary.LittleEndian.PutUint64(span.traceContext.Span[:], tx.rand.Uint64()) 146 } 147 span.stackStackTraceMinDuration = tx.spanStackTraceMinDuration 148 span.stackTraceLimit = tx.stackTraceLimit 149 span.compressedSpan.options = tx.compressedSpan.options 150 span.exitSpanMinDuration = tx.exitSpanMinDuration 151 tx.spansCreated++ 152 } 153 154 if tx.breakdownMetricsEnabled { 155 if span.parent != nil { 156 if !span.parent.ended() { 157 span.parent.childrenTimer.childStarted(span.timestamp) 158 } 159 } else { 160 tx.childrenTimer.childStarted(span.timestamp) 161 } 162 } 163 return span 164 } 165 166 // StartSpan returns a new Span with the specified name, type, transaction ID, 167 // and options. The parent transaction context and transaction IDs must have 168 // valid, non-zero values, or else the span will be dropped. 169 // 170 // In most cases, you should use Transaction.StartSpan or Transaction.StartSpanOptions. 171 // This method is provided for corner-cases, such as starting a span after the 172 // containing transaction's End method has been called. Spans created in this 173 // way will not have the "max spans" configuration applied, nor will they be 174 // considered in any transaction's span count. 175 func (t *Tracer) StartSpan(name, spanType string, transactionID SpanID, opts SpanOptions) *Span { 176 if opts.Parent.Trace.Validate() != nil || 177 opts.Parent.Span.Validate() != nil || 178 transactionID.Validate() != nil || 179 opts.parent.IsExitSpan() { 180 return newDroppedSpan() 181 } 182 if !opts.Parent.Options.Recorded() { 183 return newDroppedSpan() 184 } 185 var spanID SpanID 186 if opts.SpanID.Validate() == nil { 187 spanID = opts.SpanID 188 } else { 189 if _, err := cryptorand.Read(spanID[:]); err != nil { 190 return newDroppedSpan() 191 } 192 } 193 if opts.Start.IsZero() { 194 opts.Start = time.Now() 195 } 196 span := t.startSpan(name, spanType, transactionID, opts) 197 span.traceContext.Span = spanID 198 199 instrumentationConfig := t.instrumentationConfig() 200 span.stackStackTraceMinDuration = instrumentationConfig.spanStackTraceMinDuration 201 span.stackTraceLimit = instrumentationConfig.stackTraceLimit 202 span.compressedSpan.options = instrumentationConfig.compressionOptions 203 span.exitSpanMinDuration = instrumentationConfig.exitSpanMinDuration 204 if opts.ExitSpan { 205 span.exit = true 206 } 207 208 return span 209 } 210 211 // SpanOptions holds options for Transaction.StartSpanOptions and Tracer.StartSpan. 212 type SpanOptions struct { 213 // Parent, if non-zero, holds the trace context of the parent span. 214 Parent TraceContext 215 216 // SpanID holds the ID to assign to the span. If this is zero, a new ID 217 // will be generated and used instead. 218 SpanID SpanID 219 220 // Indicates whether a span is an exit span or not. All child spans 221 // will be noop spans. 222 ExitSpan bool 223 224 // parent, if non-nil, holds the parent span. 225 // 226 // This is only used if Parent is zero, and is only available to internal 227 // callers of Transaction.StartSpanOptions. 228 parent *Span 229 230 // Start is the start time of the span. If this has the zero value, 231 // time.Now() will be used instead. 232 // 233 // When a span is created using Transaction.StartSpanOptions, the 234 // span timestamp is internally calculated relative to the transaction 235 // timestamp. 236 // 237 // When Tracer.StartSpan is used, this timestamp should be pre-calculated 238 // as relative from the transaction start time, i.e. by calculating the 239 // time elapsed since the transaction started, and adding that to the 240 // transaction timestamp. Calculating the timstamp in this way will ensure 241 // monotonicity of events within a transaction. 242 Start time.Time 243 244 // Links, if non-nil, holds a list of spans linked to the span. 245 Links []SpanLink 246 } 247 248 func (t *Tracer) startSpan(name, spanType string, transactionID SpanID, opts SpanOptions) *Span { 249 sd, _ := t.spanDataPool.Get().(*SpanData) 250 if sd == nil { 251 sd = &SpanData{Duration: -1} 252 } 253 span := &Span{tracer: t, SpanData: sd} 254 span.Name = name 255 span.traceContext = opts.Parent 256 span.parentID = opts.Parent.Span 257 span.transactionID = transactionID 258 span.timestamp = opts.Start 259 span.Type = spanType 260 span.links = opts.Links 261 if dot := strings.IndexRune(spanType, '.'); dot != -1 { 262 span.Type = spanType[:dot] 263 span.Subtype = spanType[dot+1:] 264 if dot := strings.IndexRune(span.Subtype, '.'); dot != -1 { 265 span.Subtype, span.Action = span.Subtype[:dot], span.Subtype[dot+1:] 266 } 267 } 268 return span 269 } 270 271 // newDropped returns a new Span with a non-nil SpanData. 272 func newDroppedSpan() *Span { 273 span, _ := droppedSpanDataPool.Get().(*Span) 274 if span == nil { 275 span = &Span{SpanData: &SpanData{}} 276 } 277 return span 278 } 279 280 // Span describes an operation within a transaction. 281 type Span struct { 282 tracer *Tracer // nil if span is dropped 283 tx *Transaction 284 parent *Span 285 traceContext TraceContext 286 transactionID SpanID 287 parentID SpanID 288 exit bool 289 290 // ctxPropagated is set to 1 when the traceContext is propagated downstream. 291 ctxPropagated uint32 292 293 mu sync.RWMutex 294 295 // SpanData holds the span data. This field is set to nil when 296 // the span's End method is called. 297 *SpanData 298 299 // finalType and finalSubtype are set to SpanData.Type and SpanData.Subtype 300 // respectively when the span is ended. This is necessary for filtering out 301 // child spans of exit spans that have non-matching type/subtype. 302 finalType string 303 finalSubtype string 304 } 305 306 // TraceContext returns the span's TraceContext. 307 func (s *Span) TraceContext() TraceContext { 308 if s == nil { 309 return TraceContext{} 310 } 311 atomic.StoreUint32(&s.ctxPropagated, 1) 312 return s.traceContext 313 } 314 315 // SetStacktrace sets the stacktrace for the span, 316 // skipping the first skip number of frames, 317 // excluding the SetStacktrace function. 318 func (s *Span) SetStacktrace(skip int) { 319 if s == nil || s.dropped() { 320 return 321 } 322 s.mu.RLock() 323 defer s.mu.RUnlock() 324 if s.ended() { 325 return 326 } 327 s.SpanData.mu.Lock() 328 defer s.SpanData.mu.Unlock() 329 s.SpanData.setStacktrace(skip + 1) 330 } 331 332 // Dropped indicates whether or not the span is dropped, meaning it will not 333 // be included in any transaction. Spans are dropped by Transaction.StartSpan 334 // if the transaction is nil, non-sampled, or the transaction's max spans 335 // limit has been reached. 336 // 337 // Dropped may be used to avoid any expensive computation required to set 338 // the span's context. 339 func (s *Span) Dropped() bool { 340 return s == nil || s.dropped() 341 } 342 343 func (s *Span) dropped() bool { 344 return s.tracer == nil 345 } 346 347 // dropWhen unsets the tracer when the passed bool cond is `true` and returns 348 // `true` only when the span is dropped. If the span has already been dropped 349 // or the condition isn't `true`, it then returns `false`. 350 // 351 // Must be called with s.mu.Lock held to be able to write to s.tracer. 352 func (s *Span) dropWhen(cond bool) bool { 353 if s.Dropped() { 354 return false 355 } 356 if cond { 357 s.tracer = nil 358 } 359 return cond 360 } 361 362 // End marks the s as being complete; s must not be used after this. 363 // 364 // If s.Duration has not been set, End will set it to the elapsed time 365 // since the span's start time. 366 func (s *Span) End() { 367 s.mu.Lock() 368 defer s.mu.Unlock() 369 if s.ended() { 370 return 371 } 372 if s.Type == "" { 373 s.Type = "custom" 374 } 375 // Store the span type and subtype on the Span struct so we can filter 376 // out child spans of exit spans with non-matching type/subtype below. 377 s.finalType = s.Type 378 s.finalSubtype = s.Subtype 379 380 if s.parent.IsExitSpan() { 381 // Children of exit spans must not have service destination/target 382 // context, as otherwise service destination metrics will be double 383 // counted. 384 s.Context.model.Destination = nil 385 s.Context.model.Service = nil 386 387 var parentType, parentSubtype string 388 s.parent.mu.RLock() 389 if s.parent.ended() { 390 parentType = s.parent.finalType 391 parentSubtype = s.parent.finalSubtype 392 } else { 393 parentType = s.parent.Type 394 parentSubtype = s.parent.Subtype 395 } 396 s.parent.mu.RUnlock() 397 398 if s.Type != parentType || s.Subtype != parentSubtype { 399 s.dropWhen(true) 400 s.end() 401 return 402 } 403 } 404 if s.exit && !s.Context.setDestinationServiceCalled { 405 // The span was created as an exit span, but the user did not 406 // manually set the destination.service.resource 407 s.setExitSpanDestinationService() 408 } 409 410 s.updateSpanServiceTarget() 411 412 if s.Duration < 0 { 413 s.Duration = time.Since(s.timestamp) 414 } 415 if s.Outcome == "" { 416 s.Outcome = s.Context.outcome() 417 if s.Outcome == "" { 418 if s.errorCaptured { 419 s.Outcome = "failure" 420 } else { 421 s.Outcome = "success" 422 } 423 } 424 } 425 switch { 426 case s.stackStackTraceMinDuration < 0: 427 // If s.stackFramesMinDuration < 0, we never set stacktrace. 428 case s.stackStackTraceMinDuration == 0: 429 // Always set stacktrace 430 s.setStacktrace(1) 431 default: 432 if !s.dropped() && len(s.stacktrace) == 0 && 433 s.Duration >= s.stackStackTraceMinDuration { 434 s.setStacktrace(1) 435 } 436 } 437 // If this span has a parent span, lock it before proceeding to 438 // prevent deadlocking when concurrently ending parent and child. 439 if s.parent != nil { 440 s.parent.mu.Lock() 441 defer s.parent.mu.Unlock() 442 } 443 if s.tx != nil { 444 s.tx.mu.RLock() 445 defer s.tx.mu.RUnlock() 446 if !s.tx.ended() { 447 s.tx.TransactionData.mu.Lock() 448 defer s.tx.TransactionData.mu.Unlock() 449 s.reportSelfTime() 450 } 451 } 452 453 evictedSpan, cached := s.attemptCompress() 454 if evictedSpan != nil { 455 evictedSpan.end() 456 } 457 if cached { 458 // s has been cached for potential compression, and will be enqueued 459 // by a future call to attemptCompress on a sibling span, or when the 460 // parent is ended. 461 return 462 } 463 s.end() 464 } 465 466 // end represents a subset of the public `s.End()` API and will only attempt 467 // to drop the span when it's a short exit span or enqueue it in case it's not. 468 // 469 // end must only be called with from `s.End()` and `tx.End()` with `s.mu`, 470 // s.tx.mu.Rlock and s.tx.TransactionData.mu held. 471 func (s *Span) end() { 472 // After an exit span finishes (no more compression attempts), we drop it 473 // when s.duration <= `exit_span_min_duration` and increment the tx dropped 474 // count. 475 s.dropFastExitSpan() 476 477 if s.dropped() { 478 if s.tx != nil { 479 if !s.tx.ended() { 480 s.aggregateDroppedSpanStats() 481 } else { 482 s.reset(s.tx.tracer) 483 } 484 } else { 485 droppedSpanDataPool.Put(s.SpanData) 486 } 487 } else { 488 s.enqueue() 489 } 490 491 s.SpanData = nil 492 } 493 494 // ParentID returns the ID of the span's parent span or transaction. 495 func (s *Span) ParentID() SpanID { 496 if s == nil { 497 return SpanID{} 498 } 499 return s.parentID 500 } 501 502 // reportSelfTime reports the span's self-time to its transaction, and informs 503 // the parent that it has ended in order for the parent to later calculate its 504 // own self-time. 505 // 506 // This must only be called from Span.End, with s.mu.Lock held for writing and 507 // s.Duration set. 508 func (s *Span) reportSelfTime() { 509 endTime := s.timestamp.Add(s.Duration) 510 511 if s.tx.ended() || !s.tx.breakdownMetricsEnabled { 512 return 513 } 514 515 if s.parent != nil { 516 if !s.parent.ended() { 517 s.parent.childrenTimer.childEnded(endTime) 518 } 519 } else { 520 s.tx.childrenTimer.childEnded(endTime) 521 } 522 s.tx.spanTimings.add(s.Type, s.Subtype, s.Duration-s.childrenTimer.finalDuration(endTime)) 523 } 524 525 func (s *Span) enqueue() { 526 event := tracerEvent{eventType: spanEvent} 527 event.span.Span = s 528 event.span.SpanData = s.SpanData 529 select { 530 case s.tracer.events <- event: 531 default: 532 // Enqueuing a span should never block. 533 s.tracer.stats.accumulate(TracerStats{SpansDropped: 1}) 534 s.reset(s.tracer) 535 } 536 } 537 538 func (s *Span) ended() bool { 539 return s.SpanData == nil 540 } 541 542 func (s *Span) setExitSpanDestinationService() { 543 resource := s.Subtype 544 if resource == "" { 545 resource = s.Type 546 } 547 s.Context.SetDestinationService(DestinationServiceSpanContext{ 548 Resource: resource, 549 }) 550 } 551 552 func (s *Span) updateSpanServiceTarget() { 553 if !s.exit { 554 // span.context.service.target.* fields should be omitted for non-exit spans. 555 s.Context.model.Service = nil 556 return 557 } 558 559 fallbackType := s.Subtype 560 if fallbackType == "" { 561 fallbackType = s.Type 562 } 563 564 // Service target fields explicitly provided. 565 if s.Context.setServiceTargetCalled { 566 // if the user calls SetServiceTarget with a non-empty name, but empty type, 567 // we'll use the specified name and infer the type 568 if s.Context.serviceTarget.Type == "" && s.Context.serviceTarget.Name != "" { 569 s.Context.SetServiceTarget(ServiceTargetSpanContext{ 570 Type: fallbackType, 571 Name: s.Context.serviceTarget.Name, 572 }) 573 } 574 return 575 } 576 577 var fallbackName string 578 if s.Context.database.Type != "" { // database spans 579 fallbackName = s.Context.database.Instance 580 } else if s.Context.message.Queue != nil { // messaging spans 581 fallbackName = s.Context.message.Queue.Name 582 } else if s.Context.http.URL != nil { // http spans 583 fallbackName = s.Context.http.URL.Host 584 } 585 586 s.Context.SetServiceTarget(ServiceTargetSpanContext{ 587 Type: fallbackType, 588 Name: fallbackName, 589 }) 590 } 591 592 // IsExitSpan returns true if the span is an exit span. 593 func (s *Span) IsExitSpan() bool { 594 if s == nil { 595 return false 596 } 597 return s.exit 598 } 599 600 // aggregateDroppedSpanStats aggregates the current span into the transaction 601 // dropped spans stats timings. 602 // 603 // Must only be called from end() with s.tx.mu and s.tx.TransactionData.mu held. 604 func (s *Span) aggregateDroppedSpanStats() { 605 // An exit span would have the destination service set but in any case, we 606 // check the field value before adding an entry to the dropped spans stats. 607 service := s.Context.destinationService.Resource 608 if s.dropped() && s.IsExitSpan() && service != "" { 609 count := 1 610 if !s.composite.empty() { 611 count = s.composite.count 612 } 613 s.tx.droppedSpansStats.add(s.Context.serviceTarget.Type, s.Context.serviceTarget.Name, service, s.Outcome, count, s.Duration) 614 } 615 } 616 617 // discardable returns whether or not the span can be dropped. 618 // 619 // It should be called with s.mu held. 620 func (s *Span) discardable() bool { 621 return s.isCompressionEligible() && s.Duration < s.exitSpanMinDuration 622 } 623 624 // dropFastExitSpan drops an exit span that is discardable and increments the 625 // s.tx.spansDropped. If the transaction is nil or has ended, the span will not 626 // be dropped. 627 // 628 // Must be called with s.tx.TransactionData held. 629 func (s *Span) dropFastExitSpan() { 630 if s.tx == nil || s.tx.ended() { 631 return 632 } 633 if !s.dropWhen(s.discardable()) { 634 return 635 } 636 if !s.tx.ended() { 637 s.tx.spansCreated-- 638 s.tx.spansDropped++ 639 } 640 } 641 642 // SpanData holds the details for a span, and is embedded inside Span. 643 // When a span is ended or discarded, its SpanData field will be set 644 // to nil. 645 type SpanData struct { 646 exitSpanMinDuration time.Duration 647 stackStackTraceMinDuration time.Duration 648 stackTraceLimit int 649 timestamp time.Time 650 childrenTimer childrenTimer 651 composite compositeSpan 652 compressedSpan compressedSpan 653 654 // Name holds the span name, initialized with the value passed to StartSpan. 655 Name string 656 657 // Type holds the overarching span type, such as "db", and will be initialized 658 // with the value passed to StartSpan. 659 Type string 660 661 // Subtype holds the span subtype, such as "mysql". This will initially be empty, 662 // and can be set after starting the span. 663 Subtype string 664 665 // Action holds the span action, such as "query". This will initially be empty, 666 // and can be set after starting the span. 667 Action string 668 669 // Duration holds the span duration, initialized to -1. 670 // 671 // If you do not update Duration, calling Span.End will calculate the 672 // duration based on the elapsed time since the span's start time. 673 Duration time.Duration 674 675 // Outcome holds the span outcome: success, failure, or unknown (the default). 676 // If Outcome is set to something else, it will be replaced with "unknown". 677 // 678 // Outcome is used for error rate calculations. A value of "success" indicates 679 // that a operation succeeded, while "failure" indicates that the operation 680 // failed. If Outcome is set to "unknown" (or some other value), then the 681 // span will not be included in error rate calculations. 682 Outcome string 683 684 // Context describes the context in which span occurs. 685 Context SpanContext 686 687 links []SpanLink 688 689 mu sync.Mutex 690 stacktrace []stacktrace.Frame 691 errorCaptured bool 692 } 693 694 func (s *SpanData) setStacktrace(skip int) { 695 s.stacktrace = stacktrace.AppendStacktrace(s.stacktrace[:0], skip+1, s.stackTraceLimit) 696 } 697 698 func (s *SpanData) reset(tracer *Tracer) { 699 *s = SpanData{ 700 Context: s.Context, 701 Duration: -1, 702 stacktrace: s.stacktrace[:0], 703 } 704 s.Context.reset() 705 tracer.spanDataPool.Put(s) 706 }