Skip to content

Commit 6574a44

Browse files
committed
feat: emit metrics for command duration and errors
1 parent d2565be commit 6574a44

File tree

5 files changed

+189
-43
lines changed

5 files changed

+189
-43
lines changed

rueidisotel/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ Client side caching metrics:
1111
- `rueidis_do_cache_miss`: number of cache miss on client side
1212
- `rueidis_do_cache_hits`: number of cache hits on client side
1313

14+
Client side commmand metrics:
15+
- `rueidis_command_duration_seconds`: histogram of command duration
16+
- `rueidis_command_errors`: number of command errors
17+
1418
```golang
1519
package main
1620

rueidisotel/metrics.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,22 @@ func newClient(opts ...Option) (*otelclient, error) {
149149
if err != nil {
150150
return nil, err
151151
}
152+
cli.commandMetrics = &commandMetrics{
153+
addOpts: cli.addOpts,
154+
recordOpts: cli.recordOpts,
155+
}
156+
cli.commandMetrics.duration, err = cli.meter.Float64Histogram(
157+
"rueidis_command_duration_seconds",
158+
metric.WithUnit("s"),
159+
metric.WithExplicitBucketBoundaries(defaultHistogramBuckets...),
160+
)
161+
if err != nil {
162+
return nil, err
163+
}
164+
cli.commandMetrics.errors, err = cli.meter.Int64Counter("rueidis_command_errors")
165+
if err != nil {
166+
return nil, err
167+
}
152168
return cli, nil
153169
}
154170

rueidisotel/metrics_test.go

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,14 @@ func TestNewClientMeterError(t *testing.T) {
106106
tests := []struct {
107107
name string
108108
}{
109-
{"rueidis_dial_attempt"}, {"rueidis_dial_success"}, {"rueidis_do_cache_miss"},
110-
{"rueidis_do_cache_hits"}, {"rueidis_dial_conns"}, {"rueidis_dial_latency"},
109+
{"rueidis_dial_attempt"},
110+
{"rueidis_dial_success"},
111+
{"rueidis_do_cache_miss"},
112+
{"rueidis_do_cache_hits"},
113+
{"rueidis_dial_conns"},
114+
{"rueidis_dial_latency"},
115+
{"rueidis_command_duration_seconds"},
116+
{"rueidis_command_errors"},
111117
}
112118

113119
for _, tt := range tests {
@@ -244,29 +250,29 @@ func TestTrackDialing(t *testing.T) {
244250
})
245251
}
246252

247-
func int64CountMetric(metrics metricdata.ResourceMetrics, name string) int64 {
253+
func findMetric(metrics metricdata.ResourceMetrics, name string) metricdata.Aggregation {
248254
for _, sm := range metrics.ScopeMetrics {
249255
for _, m := range sm.Metrics {
250256
if m.Name == name {
251-
data, ok := m.Data.(metricdata.Sum[int64])
252-
if !ok {
253-
return 0
254-
}
255-
return data.DataPoints[0].Value
257+
return m.Data
256258
}
257259
}
258260
}
261+
return nil
262+
}
263+
264+
func int64CountMetric(metrics metricdata.ResourceMetrics, name string) int64 {
265+
m := findMetric(metrics, name)
266+
if data, ok := m.(metricdata.Sum[int64]); ok {
267+
return data.DataPoints[0].Value
268+
}
259269
return 0
260270
}
261271

262272
func float64HistogramMetric(metrics metricdata.ResourceMetrics, name string) float64 {
263-
for _, sm := range metrics.ScopeMetrics {
264-
for _, m := range sm.Metrics {
265-
if m.Name == name {
266-
data := m.Data.(metricdata.Histogram[float64])
267-
return data.DataPoints[0].Sum
268-
}
269-
}
273+
m := findMetric(metrics, name)
274+
if data, ok := m.(metricdata.Histogram[float64]); ok {
275+
return data.DataPoints[0].Sum
270276
}
271277
return 0
272278
}

rueidisotel/trace.go

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,30 @@ func WithDBStatement(f StatementFunc) Option {
6161
// StatementFunc is a the function that maps a command's tokens to a string to put in the db.statement attribute
6262
type StatementFunc func(cmdTokens []string) string
6363

64+
type commandMetrics struct {
65+
duration metric.Float64Histogram
66+
errors metric.Int64Counter
67+
addOpts []metric.AddOption
68+
recordOpts []metric.RecordOption
69+
}
70+
71+
func (c commandMetrics) recordDuration(ctx context.Context, op string) func() {
72+
now := time.Now()
73+
return func() {
74+
opts := append(c.recordOpts, metric.WithAttributes(attribute.String("operation", op)))
75+
c.duration.Record(ctx, time.Since(now).Seconds(), opts...)
76+
}
77+
}
78+
79+
func (c commandMetrics) recordError(ctx context.Context, op string, err error) {
80+
opts := append(c.addOpts, metric.WithAttributes(attribute.String("operation", op)))
81+
if err != nil && !rueidis.IsRedisNil(err) {
82+
c.errors.Add(ctx, 1, opts...)
83+
} else {
84+
c.errors.Add(ctx, 0, opts...)
85+
}
86+
}
87+
6488
type otelclient struct {
6589
client rueidis.Client
6690
meterProvider metric.MeterProvider
@@ -74,50 +98,66 @@ type otelclient struct {
7498
addOpts []metric.AddOption
7599
recordOpts []metric.RecordOption
76100
histogramOption HistogramOption
101+
*commandMetrics
77102
}
78103

79104
func (o *otelclient) B() rueidis.Builder {
80105
return o.client.B()
81106
}
82107

83108
func (o *otelclient) Do(ctx context.Context, cmd rueidis.Completed) (resp rueidis.RedisResult) {
84-
ctx, span := o.start(ctx, first(cmd.Commands()), sum(cmd.Commands()))
109+
op := first(cmd.Commands())
110+
defer o.recordDuration(ctx, op)()
111+
ctx, span := o.start(ctx, op, sum(cmd.Commands()))
85112
if o.dbStmtFunc != nil {
86113
span.SetAttributes(dbstmt.String(o.dbStmtFunc(cmd.Commands())))
87114
}
88115

89116
resp = o.client.Do(ctx, cmd)
90117
o.end(span, resp.Error())
118+
o.recordError(ctx, op, resp.Error())
91119
return
92120
}
93121

94122
func (o *otelclient) DoMulti(ctx context.Context, multi ...rueidis.Completed) (resp []rueidis.RedisResult) {
95-
ctx, span := o.start(ctx, multiFirst(multi), multiSum(multi))
123+
op := multiFirst(multi)
124+
defer o.recordDuration(ctx, op)()
125+
ctx, span := o.start(ctx, op, multiSum(multi))
96126
resp = o.client.DoMulti(ctx, multi...)
97-
o.end(span, firstError(resp))
127+
err := firstError(resp)
128+
o.end(span, err)
129+
o.recordError(ctx, op, err)
98130
return
99131
}
100132

101133
func (o *otelclient) DoStream(ctx context.Context, cmd rueidis.Completed) (resp rueidis.RedisResultStream) {
102-
ctx, span := o.start(ctx, first(cmd.Commands()), sum(cmd.Commands()))
134+
op := first(cmd.Commands())
135+
defer o.recordDuration(ctx, op)()
136+
ctx, span := o.start(ctx, op, sum(cmd.Commands()))
103137
if o.dbStmtFunc != nil {
104138
span.SetAttributes(dbstmt.String(o.dbStmtFunc(cmd.Commands())))
105139
}
106140

107141
resp = o.client.DoStream(ctx, cmd)
108142
o.end(span, resp.Error())
143+
o.recordError(ctx, op, resp.Error())
109144
return
110145
}
111146

112147
func (o *otelclient) DoMultiStream(ctx context.Context, multi ...rueidis.Completed) (resp rueidis.MultiRedisResultStream) {
113-
ctx, span := o.start(ctx, multiFirst(multi), multiSum(multi))
148+
op := multiFirst(multi)
149+
defer o.recordDuration(ctx, op)()
150+
ctx, span := o.start(ctx, op, multiSum(multi))
114151
resp = o.client.DoMultiStream(ctx, multi...)
115152
o.end(span, resp.Error())
153+
o.recordError(ctx, op, resp.Error())
116154
return
117155
}
118156

119157
func (o *otelclient) DoCache(ctx context.Context, cmd rueidis.Cacheable, ttl time.Duration) (resp rueidis.RedisResult) {
120-
ctx, span := o.start(ctx, first(cmd.Commands()), sum(cmd.Commands()))
158+
op := first(cmd.Commands())
159+
defer o.recordDuration(ctx, op)()
160+
ctx, span := o.start(ctx, op, sum(cmd.Commands()))
121161
if o.dbStmtFunc != nil {
122162
span.SetAttributes(dbstmt.String(o.dbStmtFunc(cmd.Commands())))
123163
}
@@ -131,11 +171,14 @@ func (o *otelclient) DoCache(ctx context.Context, cmd rueidis.Cacheable, ttl tim
131171
}
132172
}
133173
o.end(span, resp.Error())
174+
o.recordError(ctx, op, resp.Error())
134175
return
135176
}
136177

137178
func (o *otelclient) DoMultiCache(ctx context.Context, multi ...rueidis.CacheableTTL) (resps []rueidis.RedisResult) {
138-
ctx, span := o.start(ctx, multiCacheableFirst(multi), multiCacheableSum(multi))
179+
op := multiCacheableFirst(multi)
180+
defer o.recordDuration(ctx, op)()
181+
ctx, span := o.start(ctx, op, multiCacheableSum(multi))
139182
resps = o.client.DoMultiCache(ctx, multi...)
140183
for _, resp := range resps {
141184
if resp.NonRedisError() == nil {
@@ -146,28 +189,32 @@ func (o *otelclient) DoMultiCache(ctx context.Context, multi ...rueidis.Cacheabl
146189
}
147190
}
148191
}
149-
o.end(span, firstError(resps))
192+
err := firstError(resps)
193+
o.end(span, err)
194+
o.recordError(ctx, op, err)
150195
return
151196
}
152197

153198
func (o *otelclient) Dedicated(fn func(rueidis.DedicatedClient) error) (err error) {
154199
return o.client.Dedicated(func(client rueidis.DedicatedClient) error {
155200
return fn(&dedicated{
156-
client: client,
157-
tAttrs: o.tAttrs,
158-
tracer: o.tracer,
159-
dbStmtFunc: o.dbStmtFunc,
201+
client: client,
202+
tAttrs: o.tAttrs,
203+
tracer: o.tracer,
204+
dbStmtFunc: o.dbStmtFunc,
205+
commandMetrics: o.commandMetrics,
160206
})
161207
})
162208
}
163209

164210
func (o *otelclient) Dedicate() (rueidis.DedicatedClient, func()) {
165211
client, cancel := o.client.Dedicate()
166212
return &dedicated{
167-
client: client,
168-
tAttrs: o.tAttrs,
169-
tracer: o.tracer,
170-
dbStmtFunc: o.dbStmtFunc,
213+
client: client,
214+
tAttrs: o.tAttrs,
215+
tracer: o.tracer,
216+
dbStmtFunc: o.dbStmtFunc,
217+
commandMetrics: o.commandMetrics,
171218
}, cancel
172219
}
173220

@@ -198,6 +245,7 @@ func (o *otelclient) Nodes() map[string]rueidis.Client {
198245
tAttrs: o.tAttrs,
199246
histogramOption: o.histogramOption,
200247
dbStmtFunc: o.dbStmtFunc,
248+
commandMetrics: o.commandMetrics,
201249
}
202250
}
203251
return nodes
@@ -218,27 +266,35 @@ type dedicated struct {
218266
tracer trace.Tracer
219267
tAttrs trace.SpanStartEventOption
220268
dbStmtFunc StatementFunc
269+
*commandMetrics
221270
}
222271

223272
func (d *dedicated) B() rueidis.Builder {
224273
return d.client.B()
225274
}
226275

227276
func (d *dedicated) Do(ctx context.Context, cmd rueidis.Completed) (resp rueidis.RedisResult) {
228-
ctx, span := d.start(ctx, first(cmd.Commands()), sum(cmd.Commands()))
277+
op := first(cmd.Commands())
278+
defer d.recordDuration(ctx, op)()
279+
ctx, span := d.start(ctx, op, sum(cmd.Commands()))
229280
if d.dbStmtFunc != nil {
230281
span.SetAttributes(dbstmt.String(d.dbStmtFunc(cmd.Commands())))
231282
}
232283

233284
resp = d.client.Do(ctx, cmd)
234285
d.end(span, resp.Error())
286+
d.recordError(ctx, op, resp.Error())
235287
return
236288
}
237289

238290
func (d *dedicated) DoMulti(ctx context.Context, multi ...rueidis.Completed) (resp []rueidis.RedisResult) {
239-
ctx, span := d.start(ctx, multiFirst(multi), multiSum(multi))
291+
op := multiFirst(multi)
292+
defer d.recordDuration(ctx, op)()
293+
ctx, span := d.start(ctx, op, multiSum(multi))
240294
resp = d.client.DoMulti(ctx, multi...)
241-
d.end(span, firstError(resp))
295+
err := firstError(resp)
296+
d.end(span, err)
297+
d.recordError(ctx, op, err)
242298
return
243299
}
244300

0 commit comments

Comments
 (0)