/
baseplate_hooks.go
143 lines (122 loc) · 3.75 KB
/
baseplate_hooks.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
package metricsbp
import (
"fmt"
"strings"
"time"
"github.com/reddit/baseplate.go/tracing"
)
const (
baseplateTimer = "baseplate.%s.latency"
baseplateCounter = "baseplate.%s.rate"
)
// CreateServerSpanHook registers each Server Span with a MetricsSpanHook.
type CreateServerSpanHook struct {
// Optional, will fallback to M when it's nil.
Metrics *Statsd
}
// OnCreateServerSpan registers MetricSpanHooks on a server Span.
func (h CreateServerSpanHook) OnCreateServerSpan(span *tracing.Span) error {
statsd := h.Metrics.fallback()
span.AddHooks(
newSpanHook(statsd, span),
countActiveRequestsHook{
metrics: statsd,
},
)
return nil
}
// spanHook wraps a Span in a timer and records a counter metric when the Span
// ends, with success=True/False tags for the counter based on whether an error
// was passed to `span.End` or not.
type spanHook struct {
metrics *Statsd
startTime time.Time
}
func newSpanHook(metrics *Statsd, span *tracing.Span) *spanHook {
return &spanHook{
metrics: metrics,
}
}
// OnCreateChild registers a child MetricsSpanHook on the child Span and starts
// a new Timer around the Span.
func (h *spanHook) OnCreateChild(parent, child *tracing.Span) error {
child.AddHooks(newSpanHook(h.metrics, child))
return nil
}
func splitClientSpanName(name string) (client, endpoint string) {
index := strings.LastIndexByte(name, '.')
if index < 0 {
// No client name
return "", name
}
return name[:index], name[index+1:]
}
// OnPostStart records the start time for the timer,
// and also sets the endpoint and client tags for the span.
func (h *spanHook) OnPostStart(span *tracing.Span) error {
if span.SpanType() == tracing.SpanTypeClient {
client, endpoint := splitClientSpanName(span.Name())
span.SetTag(tracing.TagKeyClient, client)
span.SetTag(tracing.TagKeyEndpoint, endpoint)
} else {
span.SetTag(tracing.TagKeyEndpoint, span.Name())
}
if span.StartTime().IsZero() {
h.startTime = time.Now()
} else {
h.startTime = span.StartTime()
}
return nil
}
// OnPreStop stops the Timer started in OnPostStart and records a metric
// indicating if the span was a "success" or "failure".
//
// A span is marked as "failure" if `err != nil` otherwise it is marked as
// "success".
func (h *spanHook) OnPreStop(span *tracing.Span, err error) error {
stop := span.StopTime()
if stop.IsZero() {
stop = time.Now()
}
typeStr := span.SpanType().String()
tags := Tags(span.MetricsTags())
timer := NewTimer(h.metrics.Timing(
fmt.Sprintf(baseplateTimer, typeStr),
).With(tags.AsStatsdTags()...))
timer.OverrideStartTime(h.startTime).ObserveWithEndTime(stop)
tags[tracing.TagKeySuccess] = BoolString(err == nil)
h.metrics.Counter(fmt.Sprintf(baseplateCounter, typeStr)).With(tags.AsStatsdTags()...).Add(1)
return nil
}
// OnAddCounter will increment a metric by "delta" using "key" as the metric
// "name"
func (h *spanHook) OnAddCounter(span *tracing.Span, key string, delta float64) error {
h.metrics.Counter(key).With(Tags(span.MetricsTags()).AsStatsdTags()...).Add(delta)
return nil
}
type countActiveRequestsHook struct {
metrics *Statsd
}
func (h countActiveRequestsHook) OnPostStart(_ *tracing.Span) error {
h.metrics.incActiveRequests()
return nil
}
func (h countActiveRequestsHook) OnPreStop(_ *tracing.Span, _ error) error {
h.metrics.decActiveRequests()
return nil
}
var (
_ tracing.CreateServerSpanHook = CreateServerSpanHook{}
_ tracing.StartStopSpanHook = (*spanHook)(nil)
_ tracing.CreateChildSpanHook = (*spanHook)(nil)
_ tracing.AddSpanCounterHook = (*spanHook)(nil)
_ tracing.StartStopSpanHook = countActiveRequestsHook{}
)
// BoolString returns the string version of a boolean value that should be used
// in a statsd metric tag.
func BoolString(b bool) string {
if b {
return "True"
}
return "False"
}