-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstrument.go
More file actions
244 lines (197 loc) · 6.4 KB
/
Copy pathinstrument.go
File metadata and controls
244 lines (197 loc) · 6.4 KB
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
package stats
import "fmt"
// InstrumentVector is a multi-dimensional instrument that creates instrument instances
// with specific label values.
type InstrumentVector interface {
// WithLabels returns an Instrument with the specified label values.
// The number of values must match the number of labels defined for this vector.
WithLabels(...string) Instrument
}
type instrumentVector struct {
entityVector
scope Scope
name string
opts instrumentOptions
}
// NewInstrumentVector creates a new instrument vector with the given scope, name, labels, and options.
func NewInstrumentVector(scope Scope, name string, labels []string, opts ...InstrumentOption) InstrumentVector {
if _, ok := scope.(noopScope); ok {
return NoopInstrumentVector
}
var iv = instrumentVector{
entityVector: entityVector{
marshaler: newDefaultMarshaler(),
labels: labels,
},
scope: scope,
name: name,
opts: defaultInstrumentOptions,
}
for _, opt := range opts {
opt(&iv.opts)
}
iv.newFunc = iv.newInstrument
return &iv
}
func (iv *instrumentVector) newInstrument(vs map[string]string) interface{} {
return newInstrument(iv.scope.Scope("", vs), iv.name, iv.opts)
}
func (iv *instrumentVector) WithLabels(ls ...string) Instrument {
return iv.entity(ls).(*instrument)
}
// Instrument provides automatic instrumentation for function execution.
// It tracks three metrics automatically:
// - <name>_started_total: counter of operation starts
// - <name>_total{status="..."}: counter of completions by status
// - <name>_duration_seconds: histogram of execution durations
//
// The started counter is useful for computing in-flight operations:
// in_flight = started_total - sum(total)
type Instrument interface {
// Exec executes the given function and records metrics.
// Returns the error from the function unchanged.
Exec(func() error) error
}
// InstrumentOption configures an Instrument with custom settings.
type InstrumentOption func(*instrumentOptions)
var defaultInstrumentOptions = instrumentOptions{
formatter: defaultFormatter,
trackStarted: true,
trackDuration: true,
counterLabel: "status",
}
// DisableStartedCounter disables tracking of the started counter.
// Use this when you only care about completions and duration, not in-flight count.
func DisableStartedCounter() InstrumentOption {
return func(opts *instrumentOptions) {
opts.trackStarted = false
}
}
// DisableDurationTracking disables tracking of operation duration.
// Use this when you only care about counters, not timing.
func DisableDurationTracking() InstrumentOption {
return func(opts *instrumentOptions) {
opts.trackDuration = false
}
}
// WithFormatter configures a custom error formatter to categorize errors.
// The formatter converts errors into status label values.
// Default formatter returns "success" for nil and "failed" for any error.
func WithFormatter(f ErrorFormatter) InstrumentOption {
return func(opts *instrumentOptions) {
opts.formatter = f
}
}
// WithCounterLabel configures a custom label name for the status label.
// Default is "status".
func WithCounterLabel(s string) InstrumentOption {
return func(opts *instrumentOptions) {
opts.counterLabel = s
}
}
// WithTimerOptions configures the underlying timer with custom options.
func WithTimerOptions(tOpts ...TimerOption) InstrumentOption {
return func(opts *instrumentOptions) {
opts.tOpts = tOpts
}
}
type instrumentOptions struct {
formatter ErrorFormatter
tOpts []TimerOption
trackStarted bool
trackDuration bool
counterLabel string
}
// NewInstrument creates a new instrument with the given scope, name, and options.
//
// The instrument automatically creates three metrics:
// - <name>_started_total: counter (optional, see DisableStartedCounter)
// - <name>_total{status="..."}: counter with status label
// - <name>_duration_seconds: histogram (optional, see DisableDurationTracking)
func NewInstrument(scope Scope, name string, iOpts ...InstrumentOption) Instrument {
if _, ok := scope.(noopScope); ok {
return NoopInstrument
}
var opts = defaultInstrumentOptions
for _, opt := range iOpts {
opt(&opts)
}
return newInstrument(scope, name, opts)
}
func newInstrument(scope Scope, name string, opts instrumentOptions) *instrument {
var (
startedCounter Counter = noopCounter{}
timer Timer = noopTimer{}
)
if opts.trackStarted {
startedCounter = scope.Counter(fmt.Sprintf("%s_started_total", name))
}
if opts.trackDuration {
timer = NewTimer(
scope,
fmt.Sprintf("%s_duration", name),
opts.tOpts...,
)
}
return &instrument{
instrumentOptions: opts,
timer: timer,
started: startedCounter,
finished: scope.CounterVector(
fmt.Sprintf("%s_total", name),
[]string{opts.counterLabel},
),
}
}
// ErrorFormatter converts an error into a status label value.
// Used by Instrument to categorize operation results.
type ErrorFormatter func(error) string
type instrument struct {
instrumentOptions
finished CounterVector
started Counter
timer Timer
}
func (i *instrument) Exec(fn func() error) error {
i.started.Inc()
sw := i.timer.Start()
err := fn()
sw.Stop()
i.finished.WithLabels(i.formatter(err)).Inc()
return err
}
// defaultFormatter , look into https://github.com/upfluence/errors/blob/master/stats/statuser.go#L36
// for more advanced reporting
func defaultFormatter(err error) string {
if err == nil {
return "success"
}
return "failed"
}
// ExecInstrument2 is a generic wrapper around Instrument.Exec that handles functions
// returning both a value and an error. This is a convenience function for instrumenting
// operations that return results.
func ExecInstrument2[T any](i Instrument, fn func() (T, error)) (T, error) {
var res T
return res, i.Exec(func() error {
var err error
res, err = fn()
return err
})
}
type noopInstrument struct{}
func (n noopInstrument) Exec(fn func() error) error {
return fn()
}
type noopInstrumentVector struct{}
func (n noopInstrumentVector) WithLabels(...string) Instrument {
return noopInstrument{}
}
var (
// NoopInstrument is an instrument that discards all operations.
// Useful for testing or when instrumentation is conditionally disabled.
NoopInstrument Instrument = noopInstrument{}
// NoopInstrumentVector is an instrument vector that returns noop instruments.
// Useful for testing or when instrumentation is conditionally disabled.
NoopInstrumentVector InstrumentVector = noopInstrumentVector{}
)