This repository has been archived by the owner on Oct 29, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 137
/
span.go
264 lines (235 loc) · 6.22 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
package appdash
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"strings"
"sourcegraph.com/sourcegraph/appdash/internal/wire"
)
// A SpanID refers to a single span.
type SpanID struct {
// Trace is the root ID of the tree that contains all of the spans
// related to this one.
Trace ID
// Span is an ID that probabilistically uniquely identifies this
// span.
Span ID
// Parent is the ID of the parent span, if any.
Parent ID
}
var (
// ErrBadSpanID is returned when the span ID cannot be parsed.
ErrBadSpanID = errors.New("bad span ID")
)
// String returns the SpanID as a slash-separated, set of hex-encoded
// parameters (root, ID, parent). If the SpanID has no parent, that value is
// elided.
func (id SpanID) String() string {
if id.Parent == 0 {
return fmt.Sprintf("%s%s%s", id.Trace, SpanIDDelimiter, id.Span)
}
return fmt.Sprintf(
"%s%s%s%s%s",
id.Trace,
SpanIDDelimiter,
id.Span,
SpanIDDelimiter,
id.Parent,
)
}
// Format formats according to a format specifier and returns the
// resulting string. The receiver's string representation is the first
// argument.
func (id SpanID) Format(s string, args ...interface{}) string {
args = append([]interface{}{id.String()}, args...)
return fmt.Sprintf(s, args...)
}
// IsRoot returns whether id is the root ID of a trace.
func (id SpanID) IsRoot() bool {
return id.Parent == 0
}
// wire returns the span ID as it's protobuf definition.
func (id SpanID) wire() *wire.CollectPacket_SpanID {
return &wire.CollectPacket_SpanID{
Trace: (*uint64)(&id.Trace),
Span: (*uint64)(&id.Span),
Parent: (*uint64)(&id.Parent),
}
}
// spanIDFromWire returns a SpanID from it's protobuf definition.
func spanIDFromWire(w *wire.CollectPacket_SpanID) SpanID {
return SpanID{
Trace: ID(*w.Trace),
Span: ID(*w.Span),
Parent: ID(*w.Parent),
}
}
// NewRootSpanID generates a new span ID for a root span. This should
// only be used to generate entries for spans caused exclusively by
// spans which are outside of your system as a whole (e.g., a root
// span for the first time you see a user request).
func NewRootSpanID() SpanID {
return SpanID{
Trace: generateID(),
Span: generateID(),
}
}
// NewSpanID returns a new ID for an span which is the child of the
// given parent ID. This should be used to track causal relationships
// between spans.
func NewSpanID(parent SpanID) SpanID {
return SpanID{
Trace: parent.Trace,
Span: generateID(),
Parent: parent.Span,
}
}
const (
// SpanIDDelimiter is the delimiter used to concatenate an
// SpanID's components.
SpanIDDelimiter = "/"
)
// ParseSpanID parses the given string as a slash-separated set of parameters.
func ParseSpanID(s string) (*SpanID, error) {
parts := strings.Split(s, SpanIDDelimiter)
if len(parts) != 2 && len(parts) != 3 {
return nil, ErrBadSpanID
}
root, err := ParseID(parts[0])
if err != nil {
return nil, ErrBadSpanID
}
id, err := ParseID(parts[1])
if err != nil {
return nil, ErrBadSpanID
}
var parent ID
if len(parts) == 3 {
i, err := ParseID(parts[2])
if err != nil {
return nil, ErrBadSpanID
}
parent = i
}
return &SpanID{
Trace: root,
Span: id,
Parent: parent,
}, nil
}
// Span is a span ID and its annotations.
type Span struct {
// ID probabilistically uniquely identifies this span.
ID SpanID
Annotations
}
// String returns the Span as a formatted string.
func (s *Span) String() string {
b, err := json.MarshalIndent(s, "", " ")
if err != nil {
panic(err)
}
return string(b)
}
// Name returns a span's name if it has a name annotation, and ""
// otherwise.
func (s *Span) Name() string {
for _, ann := range s.Annotations {
if ann.Key == "Name" {
return string(ann.Value)
}
}
return ""
}
// Annotations is a list of annotations (on a span).
type Annotations []Annotation
// An Annotation is an arbitrary key-value property on a span.
type Annotation struct {
// Key is the annotation's key.
Key string
// Value is the annotation's value, which may be either human or
// machine readable, depending on the schema of the event that
// generated it.
Value []byte
}
// Important determines if this annotation's key is considered important to any
// of the registered event types.
func (a Annotation) Important() bool {
for _, ev := range registeredEvents {
i, ok := ev.(ImportantEvent)
if !ok {
continue
}
for _, k := range i.Important() {
if a.Key == k {
return true
}
}
}
return false
}
// String returns a formatted list of annotations.
func (as Annotations) String() string {
var buf bytes.Buffer
for _, a := range as {
fmt.Fprintf(&buf, "%s=%q\n", a.Key, a.Value)
}
return buf.String()
}
// schemas returns a list of schema types in the annotations.
func (as Annotations) schemas() []string {
var schemas []string
for _, a := range as {
if strings.HasPrefix(a.Key, SchemaPrefix) {
schemas = append(schemas, a.Key[len(SchemaPrefix):])
}
}
return schemas
}
// get gets the value of the first annotation with the given key, or
// nil if none exists. There may be multiple annotations with the key;
// only the first's value is returned.
func (as Annotations) get(key string) []byte {
for _, a := range as {
if a.Key == key {
return a.Value
}
}
return nil
}
// StringMap returns the annotations as a key-value map. Only one
// annotation for a key appears in the map, and it is chosen
// arbitrarily among the annotations with the same key.
func (as Annotations) StringMap() map[string]string {
m := make(map[string]string, len(as))
for _, a := range as {
m[a.Key] = string(a.Value)
}
return m
}
// wire returns the set of annotations as their protobuf definitions.
func (as Annotations) wire() (w []*wire.CollectPacket_Annotation) {
for _, a := range as {
// Important: Make a copy of a that we can retain a pointer to that
// doesn't change after each iteration. Otherwise all wire annotations
// would have the same key.
cpy := a
w = append(w, &wire.CollectPacket_Annotation{
Key: &cpy.Key,
Value: cpy.Value,
})
}
return
}
// annotationsFromWire returns Annotations from their protobuf definitions.
func annotationsFromWire(as []*wire.CollectPacket_Annotation) Annotations {
var w Annotations
for _, a := range as {
w = append(w, Annotation{
Key: *a.Key,
Value: a.Value,
})
}
return w
}