You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
516 lines
14 KiB
516 lines
14 KiB
// Copyright 2017, OpenCensus Authors |
|
// |
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
|
// you may not use this file except in compliance with the License. |
|
// You may obtain a copy of the License at |
|
// |
|
// http://www.apache.org/licenses/LICENSE-2.0 |
|
// |
|
// Unless required by applicable law or agreed to in writing, software |
|
// distributed under the License is distributed on an "AS IS" BASIS, |
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
// See the License for the specific language governing permissions and |
|
// limitations under the License. |
|
|
|
package trace |
|
|
|
import ( |
|
"context" |
|
crand "crypto/rand" |
|
"encoding/binary" |
|
"fmt" |
|
"math/rand" |
|
"sync" |
|
"sync/atomic" |
|
"time" |
|
|
|
"go.opencensus.io/internal" |
|
) |
|
|
|
// Span represents a span of a trace. It has an associated SpanContext, and |
|
// stores data accumulated while the span is active. |
|
// |
|
// Ideally users should interact with Spans by calling the functions in this |
|
// package that take a Context parameter. |
|
type Span struct { |
|
// data contains information recorded about the span. |
|
// |
|
// It will be non-nil if we are exporting the span or recording events for it. |
|
// Otherwise, data is nil, and the Span is simply a carrier for the |
|
// SpanContext, so that the trace ID is propagated. |
|
data *SpanData |
|
mu sync.Mutex // protects the contents of *data (but not the pointer value.) |
|
spanContext SpanContext |
|
// spanStore is the spanStore this span belongs to, if any, otherwise it is nil. |
|
*spanStore |
|
endOnce sync.Once |
|
|
|
executionTracerTaskEnd func() // ends the execution tracer span |
|
} |
|
|
|
// IsRecordingEvents returns true if events are being recorded for this span. |
|
// Use this check to avoid computing expensive annotations when they will never |
|
// be used. |
|
func (s *Span) IsRecordingEvents() bool { |
|
if s == nil { |
|
return false |
|
} |
|
return s.data != nil |
|
} |
|
|
|
// TraceOptions contains options associated with a trace span. |
|
type TraceOptions uint32 |
|
|
|
// IsSampled returns true if the span will be exported. |
|
func (sc SpanContext) IsSampled() bool { |
|
return sc.TraceOptions.IsSampled() |
|
} |
|
|
|
// setIsSampled sets the TraceOptions bit that determines whether the span will be exported. |
|
func (sc *SpanContext) setIsSampled(sampled bool) { |
|
if sampled { |
|
sc.TraceOptions |= 1 |
|
} else { |
|
sc.TraceOptions &= ^TraceOptions(1) |
|
} |
|
} |
|
|
|
// IsSampled returns true if the span will be exported. |
|
func (t TraceOptions) IsSampled() bool { |
|
return t&1 == 1 |
|
} |
|
|
|
// SpanContext contains the state that must propagate across process boundaries. |
|
// |
|
// SpanContext is not an implementation of context.Context. |
|
// TODO: add reference to external Census docs for SpanContext. |
|
type SpanContext struct { |
|
TraceID TraceID |
|
SpanID SpanID |
|
TraceOptions TraceOptions |
|
} |
|
|
|
type contextKey struct{} |
|
|
|
// FromContext returns the Span stored in a context, or nil if there isn't one. |
|
func FromContext(ctx context.Context) *Span { |
|
s, _ := ctx.Value(contextKey{}).(*Span) |
|
return s |
|
} |
|
|
|
// WithSpan returns a new context with the given Span attached. |
|
// |
|
// Deprecated: Use NewContext. |
|
func WithSpan(parent context.Context, s *Span) context.Context { |
|
return NewContext(parent, s) |
|
} |
|
|
|
// NewContext returns a new context with the given Span attached. |
|
func NewContext(parent context.Context, s *Span) context.Context { |
|
return context.WithValue(parent, contextKey{}, s) |
|
} |
|
|
|
// All available span kinds. Span kind must be either one of these values. |
|
const ( |
|
SpanKindUnspecified = iota |
|
SpanKindServer |
|
SpanKindClient |
|
) |
|
|
|
// StartOptions contains options concerning how a span is started. |
|
type StartOptions struct { |
|
// Sampler to consult for this Span. If provided, it is always consulted. |
|
// |
|
// If not provided, then the behavior differs based on whether |
|
// the parent of this Span is remote, local, or there is no parent. |
|
// In the case of a remote parent or no parent, the |
|
// default sampler (see Config) will be consulted. Otherwise, |
|
// when there is a non-remote parent, no new sampling decision will be made: |
|
// we will preserve the sampling of the parent. |
|
Sampler Sampler |
|
|
|
// SpanKind represents the kind of a span. If none is set, |
|
// SpanKindUnspecified is used. |
|
SpanKind int |
|
} |
|
|
|
// StartOption apply changes to StartOptions. |
|
type StartOption func(*StartOptions) |
|
|
|
// WithSpanKind makes new spans to be created with the given kind. |
|
func WithSpanKind(spanKind int) StartOption { |
|
return func(o *StartOptions) { |
|
o.SpanKind = spanKind |
|
} |
|
} |
|
|
|
// WithSampler makes new spans to be be created with a custom sampler. |
|
// Otherwise, the global sampler is used. |
|
func WithSampler(sampler Sampler) StartOption { |
|
return func(o *StartOptions) { |
|
o.Sampler = sampler |
|
} |
|
} |
|
|
|
// StartSpan starts a new child span of the current span in the context. If |
|
// there is no span in the context, creates a new trace and span. |
|
func StartSpan(ctx context.Context, name string, o ...StartOption) (context.Context, *Span) { |
|
var opts StartOptions |
|
var parent SpanContext |
|
if p := FromContext(ctx); p != nil { |
|
parent = p.spanContext |
|
} |
|
for _, op := range o { |
|
op(&opts) |
|
} |
|
span := startSpanInternal(name, parent != SpanContext{}, parent, false, opts) |
|
|
|
ctx, end := startExecutionTracerTask(ctx, name) |
|
span.executionTracerTaskEnd = end |
|
return NewContext(ctx, span), span |
|
} |
|
|
|
// StartSpanWithRemoteParent starts a new child span of the span from the given parent. |
|
// |
|
// If the incoming context contains a parent, it ignores. StartSpanWithRemoteParent is |
|
// preferred for cases where the parent is propagated via an incoming request. |
|
func StartSpanWithRemoteParent(ctx context.Context, name string, parent SpanContext, o ...StartOption) (context.Context, *Span) { |
|
var opts StartOptions |
|
for _, op := range o { |
|
op(&opts) |
|
} |
|
span := startSpanInternal(name, parent != SpanContext{}, parent, true, opts) |
|
ctx, end := startExecutionTracerTask(ctx, name) |
|
span.executionTracerTaskEnd = end |
|
return NewContext(ctx, span), span |
|
} |
|
|
|
// NewSpan returns a new span. |
|
// |
|
// If parent is not nil, created span will be a child of the parent. |
|
// |
|
// Deprecated: Use StartSpan. |
|
func NewSpan(name string, parent *Span, o StartOptions) *Span { |
|
var parentSpanContext SpanContext |
|
if parent != nil { |
|
parentSpanContext = parent.SpanContext() |
|
} |
|
return startSpanInternal(name, parent != nil, parentSpanContext, false, o) |
|
} |
|
|
|
// NewSpanWithRemoteParent returns a new span with the given parent SpanContext. |
|
// |
|
// Deprecated: Use StartSpanWithRemoteParent. |
|
func NewSpanWithRemoteParent(name string, parent SpanContext, o StartOptions) *Span { |
|
return startSpanInternal(name, true, parent, true, o) |
|
} |
|
|
|
func startSpanInternal(name string, hasParent bool, parent SpanContext, remoteParent bool, o StartOptions) *Span { |
|
span := &Span{} |
|
span.spanContext = parent |
|
|
|
cfg := config.Load().(*Config) |
|
|
|
if !hasParent { |
|
span.spanContext.TraceID = cfg.IDGenerator.NewTraceID() |
|
} |
|
span.spanContext.SpanID = cfg.IDGenerator.NewSpanID() |
|
sampler := cfg.DefaultSampler |
|
|
|
if !hasParent || remoteParent || o.Sampler != nil { |
|
// If this span is the child of a local span and no Sampler is set in the |
|
// options, keep the parent's TraceOptions. |
|
// |
|
// Otherwise, consult the Sampler in the options if it is non-nil, otherwise |
|
// the default sampler. |
|
if o.Sampler != nil { |
|
sampler = o.Sampler |
|
} |
|
span.spanContext.setIsSampled(sampler(SamplingParameters{ |
|
ParentContext: parent, |
|
TraceID: span.spanContext.TraceID, |
|
SpanID: span.spanContext.SpanID, |
|
Name: name, |
|
HasRemoteParent: remoteParent}).Sample) |
|
} |
|
|
|
if !internal.LocalSpanStoreEnabled && !span.spanContext.IsSampled() { |
|
return span |
|
} |
|
|
|
span.data = &SpanData{ |
|
SpanContext: span.spanContext, |
|
StartTime: time.Now(), |
|
SpanKind: o.SpanKind, |
|
Name: name, |
|
HasRemoteParent: remoteParent, |
|
} |
|
if hasParent { |
|
span.data.ParentSpanID = parent.SpanID |
|
} |
|
if internal.LocalSpanStoreEnabled { |
|
var ss *spanStore |
|
ss = spanStoreForNameCreateIfNew(name) |
|
if ss != nil { |
|
span.spanStore = ss |
|
ss.add(span) |
|
} |
|
} |
|
|
|
return span |
|
} |
|
|
|
// End ends the span. |
|
func (s *Span) End() { |
|
if !s.IsRecordingEvents() { |
|
return |
|
} |
|
s.endOnce.Do(func() { |
|
if s.executionTracerTaskEnd != nil { |
|
s.executionTracerTaskEnd() |
|
} |
|
// TODO: optimize to avoid this call if sd won't be used. |
|
sd := s.makeSpanData() |
|
sd.EndTime = internal.MonotonicEndTime(sd.StartTime) |
|
if s.spanStore != nil { |
|
s.spanStore.finished(s, sd) |
|
} |
|
if s.spanContext.IsSampled() { |
|
// TODO: consider holding exportersMu for less time. |
|
exportersMu.Lock() |
|
for e := range exporters { |
|
e.ExportSpan(sd) |
|
} |
|
exportersMu.Unlock() |
|
} |
|
}) |
|
} |
|
|
|
// makeSpanData produces a SpanData representing the current state of the Span. |
|
// It requires that s.data is non-nil. |
|
func (s *Span) makeSpanData() *SpanData { |
|
var sd SpanData |
|
s.mu.Lock() |
|
sd = *s.data |
|
if s.data.Attributes != nil { |
|
sd.Attributes = make(map[string]interface{}) |
|
for k, v := range s.data.Attributes { |
|
sd.Attributes[k] = v |
|
} |
|
} |
|
s.mu.Unlock() |
|
return &sd |
|
} |
|
|
|
// SpanContext returns the SpanContext of the span. |
|
func (s *Span) SpanContext() SpanContext { |
|
if s == nil { |
|
return SpanContext{} |
|
} |
|
return s.spanContext |
|
} |
|
|
|
// SetStatus sets the status of the span, if it is recording events. |
|
func (s *Span) SetStatus(status Status) { |
|
if !s.IsRecordingEvents() { |
|
return |
|
} |
|
s.mu.Lock() |
|
s.data.Status = status |
|
s.mu.Unlock() |
|
} |
|
|
|
// AddAttributes sets attributes in the span. |
|
// |
|
// Existing attributes whose keys appear in the attributes parameter are overwritten. |
|
func (s *Span) AddAttributes(attributes ...Attribute) { |
|
if !s.IsRecordingEvents() { |
|
return |
|
} |
|
s.mu.Lock() |
|
if s.data.Attributes == nil { |
|
s.data.Attributes = make(map[string]interface{}) |
|
} |
|
copyAttributes(s.data.Attributes, attributes) |
|
s.mu.Unlock() |
|
} |
|
|
|
// copyAttributes copies a slice of Attributes into a map. |
|
func copyAttributes(m map[string]interface{}, attributes []Attribute) { |
|
for _, a := range attributes { |
|
m[a.key] = a.value |
|
} |
|
} |
|
|
|
func (s *Span) lazyPrintfInternal(attributes []Attribute, format string, a ...interface{}) { |
|
now := time.Now() |
|
msg := fmt.Sprintf(format, a...) |
|
var m map[string]interface{} |
|
s.mu.Lock() |
|
if len(attributes) != 0 { |
|
m = make(map[string]interface{}) |
|
copyAttributes(m, attributes) |
|
} |
|
s.data.Annotations = append(s.data.Annotations, Annotation{ |
|
Time: now, |
|
Message: msg, |
|
Attributes: m, |
|
}) |
|
s.mu.Unlock() |
|
} |
|
|
|
func (s *Span) printStringInternal(attributes []Attribute, str string) { |
|
now := time.Now() |
|
var a map[string]interface{} |
|
s.mu.Lock() |
|
if len(attributes) != 0 { |
|
a = make(map[string]interface{}) |
|
copyAttributes(a, attributes) |
|
} |
|
s.data.Annotations = append(s.data.Annotations, Annotation{ |
|
Time: now, |
|
Message: str, |
|
Attributes: a, |
|
}) |
|
s.mu.Unlock() |
|
} |
|
|
|
// Annotate adds an annotation with attributes. |
|
// Attributes can be nil. |
|
func (s *Span) Annotate(attributes []Attribute, str string) { |
|
if !s.IsRecordingEvents() { |
|
return |
|
} |
|
s.printStringInternal(attributes, str) |
|
} |
|
|
|
// Annotatef adds an annotation with attributes. |
|
func (s *Span) Annotatef(attributes []Attribute, format string, a ...interface{}) { |
|
if !s.IsRecordingEvents() { |
|
return |
|
} |
|
s.lazyPrintfInternal(attributes, format, a...) |
|
} |
|
|
|
// AddMessageSendEvent adds a message send event to the span. |
|
// |
|
// messageID is an identifier for the message, which is recommended to be |
|
// unique in this span and the same between the send event and the receive |
|
// event (this allows to identify a message between the sender and receiver). |
|
// For example, this could be a sequence id. |
|
func (s *Span) AddMessageSendEvent(messageID, uncompressedByteSize, compressedByteSize int64) { |
|
if !s.IsRecordingEvents() { |
|
return |
|
} |
|
now := time.Now() |
|
s.mu.Lock() |
|
s.data.MessageEvents = append(s.data.MessageEvents, MessageEvent{ |
|
Time: now, |
|
EventType: MessageEventTypeSent, |
|
MessageID: messageID, |
|
UncompressedByteSize: uncompressedByteSize, |
|
CompressedByteSize: compressedByteSize, |
|
}) |
|
s.mu.Unlock() |
|
} |
|
|
|
// AddMessageReceiveEvent adds a message receive event to the span. |
|
// |
|
// messageID is an identifier for the message, which is recommended to be |
|
// unique in this span and the same between the send event and the receive |
|
// event (this allows to identify a message between the sender and receiver). |
|
// For example, this could be a sequence id. |
|
func (s *Span) AddMessageReceiveEvent(messageID, uncompressedByteSize, compressedByteSize int64) { |
|
if !s.IsRecordingEvents() { |
|
return |
|
} |
|
now := time.Now() |
|
s.mu.Lock() |
|
s.data.MessageEvents = append(s.data.MessageEvents, MessageEvent{ |
|
Time: now, |
|
EventType: MessageEventTypeRecv, |
|
MessageID: messageID, |
|
UncompressedByteSize: uncompressedByteSize, |
|
CompressedByteSize: compressedByteSize, |
|
}) |
|
s.mu.Unlock() |
|
} |
|
|
|
// AddLink adds a link to the span. |
|
func (s *Span) AddLink(l Link) { |
|
if !s.IsRecordingEvents() { |
|
return |
|
} |
|
s.mu.Lock() |
|
s.data.Links = append(s.data.Links, l) |
|
s.mu.Unlock() |
|
} |
|
|
|
func (s *Span) String() string { |
|
if s == nil { |
|
return "<nil>" |
|
} |
|
if s.data == nil { |
|
return fmt.Sprintf("span %s", s.spanContext.SpanID) |
|
} |
|
s.mu.Lock() |
|
str := fmt.Sprintf("span %s %q", s.spanContext.SpanID, s.data.Name) |
|
s.mu.Unlock() |
|
return str |
|
} |
|
|
|
var config atomic.Value // access atomically |
|
|
|
func init() { |
|
gen := &defaultIDGenerator{} |
|
// initialize traceID and spanID generators. |
|
var rngSeed int64 |
|
for _, p := range []interface{}{ |
|
&rngSeed, &gen.traceIDAdd, &gen.nextSpanID, &gen.spanIDInc, |
|
} { |
|
binary.Read(crand.Reader, binary.LittleEndian, p) |
|
} |
|
gen.traceIDRand = rand.New(rand.NewSource(rngSeed)) |
|
gen.spanIDInc |= 1 |
|
|
|
config.Store(&Config{ |
|
DefaultSampler: ProbabilitySampler(defaultSamplingProbability), |
|
IDGenerator: gen, |
|
}) |
|
} |
|
|
|
type defaultIDGenerator struct { |
|
sync.Mutex |
|
traceIDRand *rand.Rand |
|
traceIDAdd [2]uint64 |
|
nextSpanID uint64 |
|
spanIDInc uint64 |
|
} |
|
|
|
// NewSpanID returns a non-zero span ID from a randomly-chosen sequence. |
|
// mu should be held while this function is called. |
|
func (gen *defaultIDGenerator) NewSpanID() [8]byte { |
|
gen.Lock() |
|
id := gen.nextSpanID |
|
gen.nextSpanID += gen.spanIDInc |
|
if gen.nextSpanID == 0 { |
|
gen.nextSpanID += gen.spanIDInc |
|
} |
|
gen.Unlock() |
|
var sid [8]byte |
|
binary.LittleEndian.PutUint64(sid[:], id) |
|
return sid |
|
} |
|
|
|
// NewTraceID returns a non-zero trace ID from a randomly-chosen sequence. |
|
// mu should be held while this function is called. |
|
func (gen *defaultIDGenerator) NewTraceID() [16]byte { |
|
var tid [16]byte |
|
// Construct the trace ID from two outputs of traceIDRand, with a constant |
|
// added to each half for additional entropy. |
|
gen.Lock() |
|
binary.LittleEndian.PutUint64(tid[0:8], gen.traceIDRand.Uint64()+gen.traceIDAdd[0]) |
|
binary.LittleEndian.PutUint64(tid[8:16], gen.traceIDRand.Uint64()+gen.traceIDAdd[1]) |
|
gen.Unlock() |
|
return tid |
|
}
|
|
|