blob: 1b44f3f8c2f1ba09999c3cee6678ca076f1fbb1c [file] [log] [blame]
Rohan Agrawalc32d9932020-06-15 11:01:47 +00001// Copyright (c) 2017 Uber Technologies, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package jaeger
16
17import (
18 "errors"
19 "fmt"
20 "strconv"
21 "strings"
22 "sync"
23
24 "go.uber.org/atomic"
25)
26
27const (
28 flagSampled = 1
29 flagDebug = 2
30 flagFirehose = 8
31)
32
33var (
34 errEmptyTracerStateString = errors.New("Cannot convert empty string to tracer state")
35 errMalformedTracerStateString = errors.New("String does not match tracer state format")
36
37 emptyContext = SpanContext{}
38)
39
40// TraceID represents unique 128bit identifier of a trace
41type TraceID struct {
42 High, Low uint64
43}
44
45// SpanID represents unique 64bit identifier of a span
46type SpanID uint64
47
48// SpanContext represents propagated span identity and state
49type SpanContext struct {
50 // traceID represents globally unique ID of the trace.
51 // Usually generated as a random number.
52 traceID TraceID
53
54 // spanID represents span ID that must be unique within its trace,
55 // but does not have to be globally unique.
56 spanID SpanID
57
58 // parentID refers to the ID of the parent span.
59 // Should be 0 if the current span is a root span.
60 parentID SpanID
61
62 // Distributed Context baggage. The is a snapshot in time.
63 baggage map[string]string
64
65 // debugID can be set to some correlation ID when the context is being
66 // extracted from a TextMap carrier.
67 //
68 // See JaegerDebugHeader in constants.go
69 debugID string
70
71 // samplingState is shared across all spans
72 samplingState *samplingState
73
74 // remote indicates that span context represents a remote parent
75 remote bool
76}
77
78type samplingState struct {
79 // Span context's state flags that are propagated across processes. Only lower 8 bits are used.
80 // We use an int32 instead of byte to be able to use CAS operations.
81 stateFlags atomic.Int32
82
83 // When state is not final, sampling will be retried on other span write operations,
84 // like SetOperationName / SetTag, and the spans will remain writable.
85 final atomic.Bool
86
87 // localRootSpan stores the SpanID of the first span created in this process for a given trace.
88 localRootSpan SpanID
89
90 // extendedState allows samplers to keep intermediate state.
91 // The keys and values in this map are completely opaque: interface{} -> interface{}.
92 extendedState sync.Map
93}
94
95func (s *samplingState) isLocalRootSpan(id SpanID) bool {
96 return id == s.localRootSpan
97}
98
99func (s *samplingState) setFlag(newFlag int32) {
100 swapped := false
101 for !swapped {
102 old := s.stateFlags.Load()
103 swapped = s.stateFlags.CAS(old, old|newFlag)
104 }
105}
106
107func (s *samplingState) unsetFlag(newFlag int32) {
108 swapped := false
109 for !swapped {
110 old := s.stateFlags.Load()
111 swapped = s.stateFlags.CAS(old, old&^newFlag)
112 }
113}
114
115func (s *samplingState) setSampled() {
116 s.setFlag(flagSampled)
117}
118
119func (s *samplingState) unsetSampled() {
120 s.unsetFlag(flagSampled)
121}
122
123func (s *samplingState) setDebugAndSampled() {
124 s.setFlag(flagDebug | flagSampled)
125}
126
127func (s *samplingState) setFirehose() {
128 s.setFlag(flagFirehose)
129}
130
131func (s *samplingState) setFlags(flags byte) {
132 s.stateFlags.Store(int32(flags))
133}
134
135func (s *samplingState) setFinal() {
136 s.final.Store(true)
137}
138
139func (s *samplingState) flags() byte {
140 return byte(s.stateFlags.Load())
141}
142
143func (s *samplingState) isSampled() bool {
144 return s.stateFlags.Load()&flagSampled == flagSampled
145}
146
147func (s *samplingState) isDebug() bool {
148 return s.stateFlags.Load()&flagDebug == flagDebug
149}
150
151func (s *samplingState) isFirehose() bool {
152 return s.stateFlags.Load()&flagFirehose == flagFirehose
153}
154
155func (s *samplingState) isFinal() bool {
156 return s.final.Load()
157}
158
159func (s *samplingState) extendedStateForKey(key interface{}, initValue func() interface{}) interface{} {
160 if value, ok := s.extendedState.Load(key); ok {
161 return value
162 }
163 value := initValue()
164 value, _ = s.extendedState.LoadOrStore(key, value)
165 return value
166}
167
168// ForeachBaggageItem implements ForeachBaggageItem() of opentracing.SpanContext
169func (c SpanContext) ForeachBaggageItem(handler func(k, v string) bool) {
170 for k, v := range c.baggage {
171 if !handler(k, v) {
172 break
173 }
174 }
175}
176
177// IsSampled returns whether this trace was chosen for permanent storage
178// by the sampling mechanism of the tracer.
179func (c SpanContext) IsSampled() bool {
180 return c.samplingState.isSampled()
181}
182
183// IsDebug indicates whether sampling was explicitly requested by the service.
184func (c SpanContext) IsDebug() bool {
185 return c.samplingState.isDebug()
186}
187
188// IsSamplingFinalized indicates whether the sampling decision has been finalized.
189func (c SpanContext) IsSamplingFinalized() bool {
190 return c.samplingState.isFinal()
191}
192
193// IsFirehose indicates whether the firehose flag was set
194func (c SpanContext) IsFirehose() bool {
195 return c.samplingState.isFirehose()
196}
197
198// ExtendedSamplingState returns the custom state object for a given key. If the value for this key does not exist,
199// it is initialized via initValue function. This state can be used by samplers (e.g. x.PrioritySampler).
200func (c SpanContext) ExtendedSamplingState(key interface{}, initValue func() interface{}) interface{} {
201 return c.samplingState.extendedStateForKey(key, initValue)
202}
203
204// IsValid indicates whether this context actually represents a valid trace.
205func (c SpanContext) IsValid() bool {
206 return c.traceID.IsValid() && c.spanID != 0
207}
208
209// SetFirehose enables firehose mode for this trace.
210func (c SpanContext) SetFirehose() {
211 c.samplingState.setFirehose()
212}
213
214func (c SpanContext) String() string {
215 if c.traceID.High == 0 {
216 return fmt.Sprintf("%016x:%016x:%016x:%x", c.traceID.Low, uint64(c.spanID), uint64(c.parentID), c.samplingState.stateFlags.Load())
217 }
218 return fmt.Sprintf("%016x%016x:%016x:%016x:%x", c.traceID.High, c.traceID.Low, uint64(c.spanID), uint64(c.parentID), c.samplingState.stateFlags.Load())
219}
220
221// ContextFromString reconstructs the Context encoded in a string
222func ContextFromString(value string) (SpanContext, error) {
223 var context SpanContext
224 if value == "" {
225 return emptyContext, errEmptyTracerStateString
226 }
227 parts := strings.Split(value, ":")
228 if len(parts) != 4 {
229 return emptyContext, errMalformedTracerStateString
230 }
231 var err error
232 if context.traceID, err = TraceIDFromString(parts[0]); err != nil {
233 return emptyContext, err
234 }
235 if context.spanID, err = SpanIDFromString(parts[1]); err != nil {
236 return emptyContext, err
237 }
238 if context.parentID, err = SpanIDFromString(parts[2]); err != nil {
239 return emptyContext, err
240 }
241 flags, err := strconv.ParseUint(parts[3], 10, 8)
242 if err != nil {
243 return emptyContext, err
244 }
245 context.samplingState = &samplingState{}
246 context.samplingState.setFlags(byte(flags))
247 return context, nil
248}
249
250// TraceID returns the trace ID of this span context
251func (c SpanContext) TraceID() TraceID {
252 return c.traceID
253}
254
255// SpanID returns the span ID of this span context
256func (c SpanContext) SpanID() SpanID {
257 return c.spanID
258}
259
260// ParentID returns the parent span ID of this span context
261func (c SpanContext) ParentID() SpanID {
262 return c.parentID
263}
264
265// Flags returns the bitmap containing such bits as 'sampled' and 'debug'.
266func (c SpanContext) Flags() byte {
267 return c.samplingState.flags()
268}
269
270// NewSpanContext creates a new instance of SpanContext
271func NewSpanContext(traceID TraceID, spanID, parentID SpanID, sampled bool, baggage map[string]string) SpanContext {
272 samplingState := &samplingState{}
273 if sampled {
274 samplingState.setSampled()
275 }
276
277 return SpanContext{
278 traceID: traceID,
279 spanID: spanID,
280 parentID: parentID,
281 samplingState: samplingState,
282 baggage: baggage}
283}
284
285// CopyFrom copies data from ctx into this context, including span identity and baggage.
286// TODO This is only used by interop.go. Remove once TChannel Go supports OpenTracing.
287func (c *SpanContext) CopyFrom(ctx *SpanContext) {
288 c.traceID = ctx.traceID
289 c.spanID = ctx.spanID
290 c.parentID = ctx.parentID
291 c.samplingState = ctx.samplingState
292 if l := len(ctx.baggage); l > 0 {
293 c.baggage = make(map[string]string, l)
294 for k, v := range ctx.baggage {
295 c.baggage[k] = v
296 }
297 } else {
298 c.baggage = nil
299 }
300}
301
302// WithBaggageItem creates a new context with an extra baggage item.
303func (c SpanContext) WithBaggageItem(key, value string) SpanContext {
304 var newBaggage map[string]string
305 if c.baggage == nil {
306 newBaggage = map[string]string{key: value}
307 } else {
308 newBaggage = make(map[string]string, len(c.baggage)+1)
309 for k, v := range c.baggage {
310 newBaggage[k] = v
311 }
312 newBaggage[key] = value
313 }
314 // Use positional parameters so the compiler will help catch new fields.
315 return SpanContext{c.traceID, c.spanID, c.parentID, newBaggage, "", c.samplingState, c.remote}
316}
317
318// isDebugIDContainerOnly returns true when the instance of the context is only
319// used to return the debug/correlation ID from extract() method. This happens
320// in the situation when "jaeger-debug-id" header is passed in the carrier to
321// the extract() method, but the request otherwise has no span context in it.
322// Previously this would've returned opentracing.ErrSpanContextNotFound from the
323// extract method, but now it returns a dummy context with only debugID filled in.
324//
325// See JaegerDebugHeader in constants.go
326// See TextMapPropagator#Extract
327func (c *SpanContext) isDebugIDContainerOnly() bool {
328 return !c.traceID.IsValid() && c.debugID != ""
329}
330
331// ------- TraceID -------
332
333func (t TraceID) String() string {
334 if t.High == 0 {
335 return fmt.Sprintf("%x", t.Low)
336 }
337 return fmt.Sprintf("%x%016x", t.High, t.Low)
338}
339
340// TraceIDFromString creates a TraceID from a hexadecimal string
341func TraceIDFromString(s string) (TraceID, error) {
342 var hi, lo uint64
343 var err error
344 if len(s) > 32 {
345 return TraceID{}, fmt.Errorf("TraceID cannot be longer than 32 hex characters: %s", s)
346 } else if len(s) > 16 {
347 hiLen := len(s) - 16
348 if hi, err = strconv.ParseUint(s[0:hiLen], 16, 64); err != nil {
349 return TraceID{}, err
350 }
351 if lo, err = strconv.ParseUint(s[hiLen:], 16, 64); err != nil {
352 return TraceID{}, err
353 }
354 } else {
355 if lo, err = strconv.ParseUint(s, 16, 64); err != nil {
356 return TraceID{}, err
357 }
358 }
359 return TraceID{High: hi, Low: lo}, nil
360}
361
362// IsValid checks if the trace ID is valid, i.e. not zero.
363func (t TraceID) IsValid() bool {
364 return t.High != 0 || t.Low != 0
365}
366
367// ------- SpanID -------
368
369func (s SpanID) String() string {
370 return fmt.Sprintf("%x", uint64(s))
371}
372
373// SpanIDFromString creates a SpanID from a hexadecimal string
374func SpanIDFromString(s string) (SpanID, error) {
375 if len(s) > 16 {
376 return SpanID(0), fmt.Errorf("SpanID cannot be longer than 16 hex characters: %s", s)
377 }
378 id, err := strconv.ParseUint(s, 16, 64)
379 if err != nil {
380 return SpanID(0), err
381 }
382 return SpanID(id), nil
383}