Skip to content

Commit 6f93229

Browse files
authored
feat: emit metrics for command duration and errors (#826)
1 parent d2565be commit 6f93229

File tree

5 files changed

+229
-43
lines changed

5 files changed

+229
-43
lines changed

rueidisotel/README.md

+4
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

+22
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ func WithMeterProvider(provider metric.MeterProvider) Option {
3737
}
3838
}
3939

40+
// WithOperationMetricAttr sets the operation name as an attribute for duration and error metrics.
41+
// This may cause memory usage to increase with the number of commands used.
42+
func WithOperationMetricAttr() Option {
43+
return func(cli *otelclient) {
44+
cli.commandMetrics.opAttr = true
45+
}
46+
}
47+
4048
type HistogramOption struct {
4149
Buckets []float64
4250
}
@@ -149,6 +157,20 @@ func newClient(opts ...Option) (*otelclient, error) {
149157
if err != nil {
150158
return nil, err
151159
}
160+
cli.commandMetrics.addOpts = cli.addOpts
161+
cli.commandMetrics.recordOpts = cli.recordOpts
162+
cli.commandMetrics.duration, err = cli.meter.Float64Histogram(
163+
"rueidis_command_duration_seconds",
164+
metric.WithUnit("s"),
165+
metric.WithExplicitBucketBoundaries(defaultHistogramBuckets...),
166+
)
167+
if err != nil {
168+
return nil, err
169+
}
170+
cli.commandMetrics.errors, err = cli.meter.Int64Counter("rueidis_command_errors")
171+
if err != nil {
172+
return nil, err
173+
}
152174
return cli, nil
153175
}
154176

rueidisotel/metrics_test.go

+21-15
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

+77-19
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,32 @@ 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+
opAttr bool
70+
}
71+
72+
func (c *commandMetrics) recordDuration(ctx context.Context, op string, now time.Time) {
73+
opts := c.recordOpts
74+
if c.opAttr {
75+
opts = append(c.recordOpts, metric.WithAttributeSet(attribute.NewSet(attribute.String("operation", op))))
76+
}
77+
c.duration.Record(ctx, time.Since(now).Seconds(), opts...)
78+
}
79+
80+
func (c *commandMetrics) recordError(ctx context.Context, op string, err error) {
81+
if err != nil && !rueidis.IsRedisNil(err) {
82+
opts := c.addOpts
83+
if c.opAttr {
84+
opts = append(c.addOpts, metric.WithAttributeSet(attribute.NewSet(attribute.String("operation", op))))
85+
}
86+
c.errors.Add(ctx, 1, opts...)
87+
}
88+
}
89+
6490
type otelclient struct {
6591
client rueidis.Client
6692
meterProvider metric.MeterProvider
@@ -74,50 +100,66 @@ type otelclient struct {
74100
addOpts []metric.AddOption
75101
recordOpts []metric.RecordOption
76102
histogramOption HistogramOption
103+
commandMetrics
77104
}
78105

79106
func (o *otelclient) B() rueidis.Builder {
80107
return o.client.B()
81108
}
82109

83110
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()))
111+
op := first(cmd.Commands())
112+
defer o.recordDuration(ctx, op, time.Now())
113+
ctx, span := o.start(ctx, op, sum(cmd.Commands()))
85114
if o.dbStmtFunc != nil {
86115
span.SetAttributes(dbstmt.String(o.dbStmtFunc(cmd.Commands())))
87116
}
88117

89118
resp = o.client.Do(ctx, cmd)
90119
o.end(span, resp.Error())
120+
o.recordError(ctx, op, resp.Error())
91121
return
92122
}
93123

94124
func (o *otelclient) DoMulti(ctx context.Context, multi ...rueidis.Completed) (resp []rueidis.RedisResult) {
95-
ctx, span := o.start(ctx, multiFirst(multi), multiSum(multi))
125+
op := multiFirst(multi)
126+
defer o.recordDuration(ctx, op, time.Now())
127+
ctx, span := o.start(ctx, op, multiSum(multi))
96128
resp = o.client.DoMulti(ctx, multi...)
97-
o.end(span, firstError(resp))
129+
err := firstError(resp)
130+
o.end(span, err)
131+
o.recordError(ctx, op, err)
98132
return
99133
}
100134

101135
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()))
136+
op := first(cmd.Commands())
137+
defer o.recordDuration(ctx, op, time.Now())
138+
ctx, span := o.start(ctx, op, sum(cmd.Commands()))
103139
if o.dbStmtFunc != nil {
104140
span.SetAttributes(dbstmt.String(o.dbStmtFunc(cmd.Commands())))
105141
}
106142

107143
resp = o.client.DoStream(ctx, cmd)
108144
o.end(span, resp.Error())
145+
o.recordError(ctx, op, resp.Error())
109146
return
110147
}
111148

112149
func (o *otelclient) DoMultiStream(ctx context.Context, multi ...rueidis.Completed) (resp rueidis.MultiRedisResultStream) {
113-
ctx, span := o.start(ctx, multiFirst(multi), multiSum(multi))
150+
op := multiFirst(multi)
151+
defer o.recordDuration(ctx, op, time.Now())
152+
ctx, span := o.start(ctx, op, multiSum(multi))
114153
resp = o.client.DoMultiStream(ctx, multi...)
115154
o.end(span, resp.Error())
155+
o.recordError(ctx, op, resp.Error())
116156
return
117157
}
118158

119159
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()))
160+
op := first(cmd.Commands())
161+
defer o.recordDuration(ctx, op, time.Now())
162+
ctx, span := o.start(ctx, op, sum(cmd.Commands()))
121163
if o.dbStmtFunc != nil {
122164
span.SetAttributes(dbstmt.String(o.dbStmtFunc(cmd.Commands())))
123165
}
@@ -131,11 +173,14 @@ func (o *otelclient) DoCache(ctx context.Context, cmd rueidis.Cacheable, ttl tim
131173
}
132174
}
133175
o.end(span, resp.Error())
176+
o.recordError(ctx, op, resp.Error())
134177
return
135178
}
136179

137180
func (o *otelclient) DoMultiCache(ctx context.Context, multi ...rueidis.CacheableTTL) (resps []rueidis.RedisResult) {
138-
ctx, span := o.start(ctx, multiCacheableFirst(multi), multiCacheableSum(multi))
181+
op := multiCacheableFirst(multi)
182+
defer o.recordDuration(ctx, op, time.Now())
183+
ctx, span := o.start(ctx, op, multiCacheableSum(multi))
139184
resps = o.client.DoMultiCache(ctx, multi...)
140185
for _, resp := range resps {
141186
if resp.NonRedisError() == nil {
@@ -146,28 +191,32 @@ func (o *otelclient) DoMultiCache(ctx context.Context, multi ...rueidis.Cacheabl
146191
}
147192
}
148193
}
149-
o.end(span, firstError(resps))
194+
err := firstError(resps)
195+
o.end(span, err)
196+
o.recordError(ctx, op, err)
150197
return
151198
}
152199

153200
func (o *otelclient) Dedicated(fn func(rueidis.DedicatedClient) error) (err error) {
154201
return o.client.Dedicated(func(client rueidis.DedicatedClient) error {
155202
return fn(&dedicated{
156-
client: client,
157-
tAttrs: o.tAttrs,
158-
tracer: o.tracer,
159-
dbStmtFunc: o.dbStmtFunc,
203+
client: client,
204+
tAttrs: o.tAttrs,
205+
tracer: o.tracer,
206+
dbStmtFunc: o.dbStmtFunc,
207+
commandMetrics: o.commandMetrics,
160208
})
161209
})
162210
}
163211

164212
func (o *otelclient) Dedicate() (rueidis.DedicatedClient, func()) {
165213
client, cancel := o.client.Dedicate()
166214
return &dedicated{
167-
client: client,
168-
tAttrs: o.tAttrs,
169-
tracer: o.tracer,
170-
dbStmtFunc: o.dbStmtFunc,
215+
client: client,
216+
tAttrs: o.tAttrs,
217+
tracer: o.tracer,
218+
dbStmtFunc: o.dbStmtFunc,
219+
commandMetrics: o.commandMetrics,
171220
}, cancel
172221
}
173222

@@ -198,6 +247,7 @@ func (o *otelclient) Nodes() map[string]rueidis.Client {
198247
tAttrs: o.tAttrs,
199248
histogramOption: o.histogramOption,
200249
dbStmtFunc: o.dbStmtFunc,
250+
commandMetrics: o.commandMetrics,
201251
}
202252
}
203253
return nodes
@@ -218,27 +268,35 @@ type dedicated struct {
218268
tracer trace.Tracer
219269
tAttrs trace.SpanStartEventOption
220270
dbStmtFunc StatementFunc
271+
commandMetrics
221272
}
222273

223274
func (d *dedicated) B() rueidis.Builder {
224275
return d.client.B()
225276
}
226277

227278
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()))
279+
op := first(cmd.Commands())
280+
defer d.recordDuration(ctx, op, time.Now())
281+
ctx, span := d.start(ctx, op, sum(cmd.Commands()))
229282
if d.dbStmtFunc != nil {
230283
span.SetAttributes(dbstmt.String(d.dbStmtFunc(cmd.Commands())))
231284
}
232285

233286
resp = d.client.Do(ctx, cmd)
234287
d.end(span, resp.Error())
288+
d.recordError(ctx, op, resp.Error())
235289
return
236290
}
237291

238292
func (d *dedicated) DoMulti(ctx context.Context, multi ...rueidis.Completed) (resp []rueidis.RedisResult) {
239-
ctx, span := d.start(ctx, multiFirst(multi), multiSum(multi))
293+
op := multiFirst(multi)
294+
defer d.recordDuration(ctx, op, time.Now())
295+
ctx, span := d.start(ctx, op, multiSum(multi))
240296
resp = d.client.DoMulti(ctx, multi...)
241-
d.end(span, firstError(resp))
297+
err := firstError(resp)
298+
d.end(span, err)
299+
d.recordError(ctx, op, err)
242300
return
243301
}
244302

0 commit comments

Comments
 (0)