-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
span.go
393 lines (358 loc) · 12.8 KB
/
span.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
// Copyright 2017 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package tracing
import (
"fmt"
"strings"
"time"
"github.com/cockroachdb/cockroach/pkg/util/protoutil"
"github.com/cockroachdb/cockroach/pkg/util/tracing/tracingpb"
"github.com/cockroachdb/errors"
opentracing "github.com/opentracing/opentracing-go"
otlog "github.com/opentracing/opentracing-go/log"
"golang.org/x/net/trace"
)
const (
// TagPrefix is prefixed to all tags that should be output in SHOW TRACE.
TagPrefix = "cockroach."
)
// Span is the tracing Span that we use in CockroachDB. Depending on the tracing
// configuration, it can hold anywhere between zero and three destinations for
// trace information:
//
// 1. external OpenTracing-compatible trace collector (Jaeger, Zipkin, Lightstep),
// 2. /debug/requests endpoint (net/trace package); mostly useful for local debugging
// 3. CRDB-internal trace span (powers SQL session tracing).
//
// When there is no need to allocate either of these three destinations,
// a "noop span", i.e. an immutable *Span wrapping the *Tracer, may be
// returned, to allow starting additional nontrivial Spans from the return
// value later, when direct access to the tracer may no longer be available.
//
// The CockroachDB-internal Span (crdbSpan) is more complex because
// rather than reporting to some external sink, the caller's "owner"
// must propagate the trace data back across process boundaries towards
// the root of the trace span tree; see WithParentAndAutoCollection
// and WithParentAndManualCollection, respectively.
//
// Additionally, the internal span type also supports turning on, stopping,
// and restarting its data collection (see Span.StartRecording), and this is
// used extensively in SQL session tracing.
type Span struct {
tracer *Tracer // never nil
// Internal trace Span; nil if not tracing to crdb.
// When not-nil, allocated together with the surrounding Span for
// performance.
crdb *crdbSpan
// x/net/trace.Trace instance; nil if not tracing to x/net/trace.
netTr trace.Trace
// External opentracing compatible tracer such as lightstep, zipkin, jaeger;
// zero if not using one.
ot otSpan
}
// SpanMeta is information about a Span that is not local to this
// process. Typically, SpanMeta is populated from information
// about a Span on the other end of an RPC, and is used to derive
// a child span via `Tracer.StartSpan`. For local spans, SpanMeta
// is not used, as the *Span directly can be derived from.
//
// SpanMeta contains the trace and span identifiers of the parent,
// along with additional metadata. In particular, this specifies
// whether the child should be recording, in which case the contract
// is that the recording is to be returned to the caller when the
// child finishes, so that the caller can inductively construct the
// entire trace.
type SpanMeta struct {
traceID uint64
spanID uint64
// Underlying shadow tracer info and span context (optional). This
// will only be populated when the remote Span is reporting to an
// external opentracing tracer. We hold on to the type of tracer to
// avoid mixing spans when the tracer is reconfigured, as impls are
// not typically robust to being shown spans they did not create.
shadowTracerType string
shadowCtx opentracing.SpanContext
// If set, all spans derived from this context are being recorded.
//
// NB: at the time of writing, this is only ever set to RecordingVerbose
// and only if Baggage[verboseTracingBaggageKey] is set.
recordingType RecordingType
// The Span's associated baggage.
Baggage map[string]string
}
func (sm *SpanMeta) String() string {
return fmt.Sprintf("[spanID: %d, traceID: %d]", sm.spanID, sm.traceID)
}
func (s *Span) isNoop() bool {
return s.crdb == nil && s.netTr == nil && s.ot == (otSpan{})
}
// IsVerbose returns true if the Span is verbose. See SetVerbose for details.
func (s *Span) IsVerbose() bool {
return s.crdb.recordingType() == RecordingVerbose
}
// SetVerbose toggles verbose recording on the Span, which must not be a noop
// span (see the WithForceRealSpan option).
//
// With 'true', future calls to Record are actually recorded, and any future
// descendants of this Span will do so automatically as well. This does not
// apply to past derived Spans, which may in fact be noop spans.
//
// As a side effect, calls to `SetVerbose(true)` on a span that was not already
// verbose will reset any past recording stored on this Span.
//
// When set to 'false', Record will cede to add data to the recording (though
// they may still be collected, should the Span have been set up with an
// auxiliary trace sink). This does not apply to Spans derived from this one
// when it was verbose.
func (s *Span) SetVerbose(to bool) {
// TODO(tbg): when always-on tracing is firmly established, we can remove the ugly
// caveat that SetVerbose(true) is a panic on a noop span because there will be no
// noop span.
if s.isNoop() {
panic(errors.AssertionFailedf("SetVerbose called on NoopSpan; use the WithForceRealSpan option for StartSpan"))
}
if to {
// If we're already recording (perhaps because the parent was recording when
// this Span was created), there's nothing to do. Avoid the call to enableRecording
// because it would clear the existing recording.
recType := RecordingVerbose
if recType != s.crdb.recordingType() {
s.crdb.enableRecording(nil /* parent */, recType)
}
} else {
s.crdb.disableRecording()
}
}
// GetRecording retrieves the current recording, if the Span has recording
// enabled. This can be called while spans that are part of the recording are
// still open; it can run concurrently with operations on those spans.
func (s *Span) GetRecording() Recording {
return s.crdb.getRecording(s.tracer.mode())
}
// ImportRemoteSpans adds RecordedSpan data to the recording of the given Span;
// these spans will be part of the result of GetRecording. Used to import
// recorded traces from other nodes.
func (s *Span) ImportRemoteSpans(remoteSpans []tracingpb.RecordedSpan) error {
if s.tracer.mode() == modeLegacy && s.crdb.recordingType() == RecordingOff {
return nil
}
return s.crdb.importRemoteSpans(remoteSpans)
}
// IsBlackHole returns true if events for this Span are just dropped. This
// is the case when the Span is not recording and no external tracer is configured.
// Tracing clients can use this method to figure out if they can short-circuit some
// tracing-related work that would be discarded anyway.
//
// The child of a blackhole Span is a non-recordable blackhole Span[*]. These incur
// only minimal overhead. It is therefore not worth it to call this method to avoid
// starting spans.
func (s *Span) IsBlackHole() bool {
return s.crdb.recordingType() == RecordingOff && s.netTr == nil && s.ot == (otSpan{})
}
// SpanStats are stats that can be added to a Span.
type SpanStats interface {
protoutil.Message
// StatsTags returns the stats that the object represents as a map of
// key/value tags that will be added to the Span tags. The tag keys should
// start with TagPrefix.
StatsTags() map[string]string
}
// SetSpanStats sets the stats on a Span. stats.Stats() will also be added to
// the Span tags.
//
// This is deprecated. Use RecordStructured instead.
//
// TODO(tbg): remove this in the 21.2 cycle.
func (s *Span) SetSpanStats(stats SpanStats) {
if s.isNoop() {
return
}
s.RecordStructured(stats)
s.crdb.mu.Lock()
s.crdb.mu.stats = stats
for name, value := range stats.StatsTags() {
s.setTagInner(name, value, true /* locked */)
}
s.crdb.mu.Unlock()
}
// Finish marks the Span as completed. Finishing a nil *Span is a noop.
func (s *Span) Finish() {
if s == nil {
return
}
if s.isNoop() {
return
}
finishTime := time.Now()
s.crdb.mu.Lock()
s.crdb.mu.duration = finishTime.Sub(s.crdb.startTime)
s.crdb.mu.Unlock()
if s.ot.shadowSpan != nil {
s.ot.shadowSpan.Finish()
}
if s.netTr != nil {
s.netTr.Finish()
}
s.tracer.activeSpans.Lock()
delete(s.tracer.activeSpans.m, s)
s.tracer.activeSpans.Unlock()
}
// Meta returns the information which needs to be propagated across process
// boundaries in order to derive child spans from this Span. This may return
// nil, which is a valid input to `WithParentAndManualCollection`, if the Span
// has been optimized out.
func (s *Span) Meta() *SpanMeta {
var traceID uint64
var spanID uint64
var recordingType RecordingType
var baggage map[string]string
if s.crdb != nil {
traceID, spanID = s.crdb.traceID, s.crdb.spanID
s.crdb.mu.Lock()
defer s.crdb.mu.Unlock()
n := len(s.crdb.mu.baggage)
// In the common case, we have no baggage, so avoid making an empty map.
if n > 0 {
baggage = make(map[string]string, n)
}
for k, v := range s.crdb.mu.baggage {
baggage[k] = v
}
recordingType = s.crdb.mu.recording.recordingType.load()
}
var shadowTrTyp string
var shadowCtx opentracing.SpanContext
if s.ot.shadowSpan != nil {
shadowTrTyp, _ = s.ot.shadowTr.Type()
shadowCtx = s.ot.shadowSpan.Context()
}
if traceID == 0 &&
spanID == 0 &&
shadowTrTyp == "" &&
shadowCtx == nil &&
recordingType == 0 &&
baggage == nil {
return nil
}
return &SpanMeta{
traceID: traceID,
spanID: spanID,
shadowTracerType: shadowTrTyp,
shadowCtx: shadowCtx,
recordingType: recordingType,
Baggage: baggage,
}
}
// SetOperationName sets the name of the operation.
func (s *Span) SetOperationName(operationName string) *Span {
if s.isNoop() {
return s
}
if s.ot.shadowSpan != nil {
s.ot.shadowSpan.SetOperationName(operationName)
}
s.crdb.operation = operationName
return s
}
// SetTag adds a tag to the span. If there is a pre-existing tag set for the
// key, it is overwritten.
func (s *Span) SetTag(key string, value interface{}) *Span {
if s.isNoop() {
return s
}
return s.setTagInner(key, value, false /* locked */)
}
func (s *Span) setTagInner(key string, value interface{}, locked bool) *Span {
if s.ot.shadowSpan != nil {
s.ot.shadowSpan.SetTag(key, value)
}
if s.netTr != nil {
s.netTr.LazyPrintf("%s:%v", key, value)
}
// The internal tags will be used if we start a recording on this Span.
if !locked {
s.crdb.mu.Lock()
defer s.crdb.mu.Unlock()
}
s.crdb.setTagLocked(key, value)
return s
}
// Structured is an opaque protobuf that can be attached to a trace via
// `Span.RecordStructured`. This is the only kind of data a Span carries when
// `trace.mode = background`.
type Structured interface {
protoutil.Message
}
// RecordStructured adds a Structured payload to the Span. It will be added to
// the recording even if the Span is not verbose; however it will be discarded
// if the underlying Span has been optimized out (i.e. is a noop span).
//
// The caller must not mutate the item once RecordStructured has been called.
func (s *Span) RecordStructured(item Structured) {
if s.isNoop() {
return
}
s.crdb.recordStructured(item)
if s.hasVerboseSink() {
// NB: TrimSpace avoids the trailing whitespace generated by the
// protobuf stringers.
s.Record(strings.TrimSpace(item.String()))
}
}
// Record provides a way to record free-form text into verbose spans.
//
// TODO(irfansharif): We don't currently have redactability with trace
// recordings (both here, and using RecordStructured above). We'll want to do this
// soon.
func (s *Span) Record(msg string) {
s.Recordf("%s", msg)
}
// Recordf is like Record, but accepts a format specifier.
func (s *Span) Recordf(format string, args ...interface{}) {
if !s.hasVerboseSink() {
return
}
str := fmt.Sprintf(format, args...)
if s.ot.shadowSpan != nil {
s.ot.shadowSpan.LogFields(otlog.String(tracingpb.LogMessageField, str))
}
if s.netTr != nil {
s.netTr.LazyPrintf(format, args)
}
s.crdb.record(str)
}
// hasVerboseSink returns false if there is no reason to even evaluate Record
// because the result wouldn't be used for anything.
func (s *Span) hasVerboseSink() bool {
if s.netTr == nil && s.ot == (otSpan{}) && !s.IsVerbose() {
return false
}
return true
}
// SetBaggageItem attaches "baggage" to this span, a key:value pair that's
// propagated to all future descendants of this Span. Any attached baggage
// crosses RPC boundaries, and is copied transitively for every remote
// descendant.
func (s *Span) SetBaggageItem(restrictedKey, value string) *Span {
if s.isNoop() {
return s
}
s.crdb.setBaggageItemAndTag(restrictedKey, value)
if s.ot.shadowSpan != nil {
s.ot.shadowSpan.SetBaggageItem(restrictedKey, value)
s.ot.shadowSpan.SetTag(restrictedKey, value)
}
// NB: nothing to do for net/trace.
return s
}
// Tracer exports the tracer this span was created using.
func (s *Span) Tracer() *Tracer {
return s.tracer
}