Skip to content

Commit 28292b2

Browse files
committed
Add client api configurations
1 parent fe380b9 commit 28292b2

File tree

4 files changed

+293
-21
lines changed

4 files changed

+293
-21
lines changed

extra/redisotel-native/config.go

Lines changed: 266 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,192 @@ import (
44
"go.opentelemetry.io/otel/metric"
55
)
66

7+
// MetricGroup represents a group of related metrics
8+
type MetricGroup string
9+
10+
const (
11+
// MetricGroupCommand includes command-level metrics
12+
MetricGroupCommand MetricGroup = "command"
13+
// MetricGroupConnectionBasic includes basic connection metrics
14+
MetricGroupConnectionBasic MetricGroup = "connection-basic"
15+
// MetricGroupResiliency includes resiliency metrics (errors, retries, etc.)
16+
MetricGroupResiliency MetricGroup = "resiliency"
17+
// MetricGroupConnectionAdvanced includes advanced connection metrics
18+
MetricGroupConnectionAdvanced MetricGroup = "connection-advanced"
19+
// MetricGroupStream includes stream-specific metrics
20+
MetricGroupStream MetricGroup = "stream"
21+
)
22+
23+
// HistogramAggregation represents the histogram aggregation mode
24+
type HistogramAggregation string
25+
26+
const (
27+
// HistogramAggregationExplicitBucket uses explicit bucket boundaries
28+
HistogramAggregationExplicitBucket HistogramAggregation = "explicit_bucket_histogram"
29+
// HistogramAggregationBase2Exponential uses base-2 exponential buckets
30+
HistogramAggregationBase2Exponential HistogramAggregation = "base2_exponential_bucket_histogram"
31+
)
32+
733
// config holds the configuration for the instrumentation
834
type config struct {
9-
meterProvider metric.MeterProvider
10-
histogramBuckets []float64
35+
// Core settings
36+
meterProvider metric.MeterProvider
37+
enabled bool
38+
39+
// Metric group settings
40+
enabledMetricGroups map[MetricGroup]bool
41+
42+
// Command filtering
43+
includeCommands map[string]bool // nil means include all
44+
excludeCommands map[string]bool // nil means exclude none
45+
46+
// Cardinality reduction
47+
hidePubSubChannelNames bool
48+
hideStreamNames bool
49+
50+
// Histogram settings
51+
histAggregation HistogramAggregation
52+
53+
// Bucket configurations for different histogram metrics
54+
bucketsOperationDuration []float64
55+
bucketsStreamProcessingDuration []float64
56+
bucketsConnectionCreateTime []float64
57+
bucketsConnectionWaitTime []float64
58+
bucketsConnectionUseTime []float64
1159
}
1260

1361
// defaultConfig returns the default configuration
1462
func defaultConfig() config {
1563
return config{
16-
meterProvider: nil, // Will use global otel.GetMeterProvider() if nil
17-
histogramBuckets: defaultHistogramBuckets(),
64+
meterProvider: nil, // Will use global otel.GetMeterProvider() if nil
65+
enabled: false,
66+
67+
// Default metric groups: command, connection-basic, resiliency
68+
enabledMetricGroups: map[MetricGroup]bool{
69+
MetricGroupCommand: true,
70+
MetricGroupConnectionBasic: true,
71+
MetricGroupResiliency: true,
72+
},
73+
74+
// No command filtering by default
75+
includeCommands: nil,
76+
excludeCommands: nil,
77+
78+
// Don't hide labels by default
79+
hidePubSubChannelNames: false,
80+
hideStreamNames: false,
81+
82+
// Use explicit bucket histogram by default
83+
histAggregation: HistogramAggregationExplicitBucket,
84+
85+
// Default buckets for different metrics
86+
bucketsOperationDuration: defaultOperationDurationBuckets(),
87+
bucketsStreamProcessingDuration: defaultStreamProcessingDurationBuckets(),
88+
bucketsConnectionCreateTime: defaultConnectionCreateTimeBuckets(),
89+
bucketsConnectionWaitTime: defaultConnectionWaitTimeBuckets(),
90+
bucketsConnectionUseTime: defaultConnectionUseTimeBuckets(),
1891
}
1992
}
2093

21-
// defaultHistogramBuckets returns the default histogram buckets for operation duration
94+
// isMetricGroupEnabled checks if a metric group is enabled
95+
func (c *config) isMetricGroupEnabled(group MetricGroup) bool {
96+
return c.enabledMetricGroups[group]
97+
}
98+
99+
// isCommandIncluded checks if a command should be included in metrics
100+
func (c *config) isCommandIncluded(command string) bool {
101+
// If there's an exclude list and command is in it, exclude
102+
if c.excludeCommands != nil && c.excludeCommands[command] {
103+
return false
104+
}
105+
106+
// If there's an include list, only include if command is in it
107+
if c.includeCommands != nil {
108+
return c.includeCommands[command]
109+
}
110+
111+
// No filtering, include all
112+
return true
113+
}
114+
115+
// defaultOperationDurationBuckets returns the default histogram buckets for db.client.operation.duration
22116
// These buckets are designed to capture typical Redis operation latencies:
23117
// - Sub-millisecond: 0.0001s (0.1ms), 0.0005s (0.5ms)
24118
// - Milliseconds: 0.001s (1ms), 0.005s (5ms), 0.01s (10ms), 0.05s (50ms), 0.1s (100ms)
25119
// - Sub-second: 0.5s (500ms)
26-
// - Seconds: 1s, 5s, 10s
27-
func defaultHistogramBuckets() []float64 {
120+
// - Seconds: 1s, 2.5s
121+
func defaultOperationDurationBuckets() []float64 {
122+
return []float64{
123+
0.0001, // 0.1ms
124+
0.0005, // 0.5ms
125+
0.001, // 1ms
126+
0.005, // 5ms
127+
0.01, // 10ms
128+
0.05, // 50ms
129+
0.1, // 100ms
130+
0.5, // 500ms
131+
1.0, // 1s
132+
2.5, // 2.5s
133+
}
134+
}
135+
136+
// defaultStreamProcessingDurationBuckets returns the default histogram buckets for redis.client.stream.processing_duration
137+
// Stream processing can take longer than regular operations
138+
func defaultStreamProcessingDurationBuckets() []float64 {
139+
return []float64{
140+
0.0001, // 0.1ms
141+
0.0005, // 0.5ms
142+
0.001, // 1ms
143+
0.005, // 5ms
144+
0.01, // 10ms
145+
0.05, // 50ms
146+
0.1, // 100ms
147+
0.5, // 500ms
148+
1.0, // 1s
149+
5.0, // 5s
150+
10.0, // 10s
151+
}
152+
}
153+
154+
// defaultConnectionCreateTimeBuckets returns the default histogram buckets for db.client.connection.create_time
155+
// Connection creation can take longer than regular operations
156+
func defaultConnectionCreateTimeBuckets() []float64 {
157+
return []float64{
158+
0.0001, // 0.1ms
159+
0.0005, // 0.5ms
160+
0.001, // 1ms
161+
0.005, // 5ms
162+
0.01, // 10ms
163+
0.05, // 50ms
164+
0.1, // 100ms
165+
0.5, // 500ms
166+
1.0, // 1s
167+
5.0, // 5s
168+
10.0, // 10s
169+
}
170+
}
171+
172+
// defaultConnectionWaitTimeBuckets returns the default histogram buckets for db.client.connection.wait_time
173+
// Time waiting for a connection from the pool
174+
func defaultConnectionWaitTimeBuckets() []float64 {
175+
return []float64{
176+
0.0001, // 0.1ms
177+
0.0005, // 0.5ms
178+
0.001, // 1ms
179+
0.005, // 5ms
180+
0.01, // 10ms
181+
0.05, // 50ms
182+
0.1, // 100ms
183+
0.5, // 500ms
184+
1.0, // 1s
185+
5.0, // 5s
186+
10.0, // 10s
187+
}
188+
}
189+
190+
// defaultConnectionUseTimeBuckets returns the default histogram buckets for db.client.connection.use_time
191+
// Time a connection is in use (checked out from pool)
192+
func defaultConnectionUseTimeBuckets() []float64 {
28193
return []float64{
29194
0.0001, // 0.1ms
30195
0.0005, // 0.5ms
@@ -60,10 +225,100 @@ func WithMeterProvider(provider metric.MeterProvider) Option {
60225
})
61226
}
62227

63-
// WithHistogramBuckets sets custom histogram buckets for operation duration
64-
// Buckets should be in seconds and in ascending order
65-
func WithHistogramBuckets(buckets []float64) Option {
228+
// WithEnabled enables or disables metrics emission
229+
func WithEnabled(enabled bool) Option {
230+
return optionFunc(func(c *config) {
231+
c.enabled = enabled
232+
})
233+
}
234+
235+
// WithEnabledMetricGroups sets which metric groups to register
236+
// Default: ["command", "connection-basic", "resiliency"]
237+
func WithEnabledMetricGroups(groups []MetricGroup) Option {
238+
return optionFunc(func(c *config) {
239+
c.enabledMetricGroups = make(map[MetricGroup]bool)
240+
for _, group := range groups {
241+
c.enabledMetricGroups[group] = true
242+
}
243+
})
244+
}
245+
246+
// WithIncludeCommands sets a command allow-list for metrics
247+
// Only commands in this list will have metrics recorded
248+
// If not set, all commands are included (unless excluded)
249+
func WithIncludeCommands(commands []string) Option {
250+
return optionFunc(func(c *config) {
251+
c.includeCommands = make(map[string]bool)
252+
for _, cmd := range commands {
253+
c.includeCommands[cmd] = true
254+
}
255+
})
256+
}
257+
258+
// WithExcludeCommands sets a command deny-list for metrics
259+
// Commands in this list will not have metrics recorded
260+
func WithExcludeCommands(commands []string) Option {
261+
return optionFunc(func(c *config) {
262+
c.excludeCommands = make(map[string]bool)
263+
for _, cmd := range commands {
264+
c.excludeCommands[cmd] = true
265+
}
266+
})
267+
}
268+
269+
// WithHidePubSubChannelNames omits channel label from Pub/Sub metrics to reduce cardinality
270+
func WithHidePubSubChannelNames(hide bool) Option {
271+
return optionFunc(func(c *config) {
272+
c.hidePubSubChannelNames = hide
273+
})
274+
}
275+
276+
// WithHideStreamNames omits stream label from stream metrics to reduce cardinality
277+
func WithHideStreamNames(hide bool) Option {
278+
return optionFunc(func(c *config) {
279+
c.hideStreamNames = hide
280+
})
281+
}
282+
283+
// WithHistogramAggregation sets the histogram aggregation mode
284+
// Controls whether bucket overrides apply
285+
func WithHistogramAggregation(agg HistogramAggregation) Option {
286+
return optionFunc(func(c *config) {
287+
c.histAggregation = agg
288+
})
289+
}
290+
291+
// WithBucketsOperationDuration sets explicit buckets (seconds) for db.client.operation.duration
292+
func WithBucketsOperationDuration(buckets []float64) Option {
293+
return optionFunc(func(c *config) {
294+
c.bucketsOperationDuration = buckets
295+
})
296+
}
297+
298+
// WithBucketsStreamProcessingDuration sets explicit buckets (seconds) for redis.client.stream.processing_duration
299+
func WithBucketsStreamProcessingDuration(buckets []float64) Option {
300+
return optionFunc(func(c *config) {
301+
c.bucketsStreamProcessingDuration = buckets
302+
})
303+
}
304+
305+
// WithBucketsConnectionCreateTime sets buckets for db.client.connection.create_time
306+
func WithBucketsConnectionCreateTime(buckets []float64) Option {
307+
return optionFunc(func(c *config) {
308+
c.bucketsConnectionCreateTime = buckets
309+
})
310+
}
311+
312+
// WithBucketsConnectionWaitTime sets buckets for db.client.connection.wait_time
313+
func WithBucketsConnectionWaitTime(buckets []float64) Option {
314+
return optionFunc(func(c *config) {
315+
c.bucketsConnectionWaitTime = buckets
316+
})
317+
}
318+
319+
// WithBucketsConnectionUseTime sets buckets for db.client.connection.use_time
320+
func WithBucketsConnectionUseTime(buckets []float64) Option {
66321
return optionFunc(func(c *config) {
67-
c.histogramBuckets = buckets
322+
c.bucketsConnectionUseTime = buckets
68323
})
69324
}

extra/redisotel-native/redisotel.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,19 @@ func initOnce(client redis.UniversalClient, opts ...Option) error {
115115
)
116116

117117
// Create histogram for operation duration
118-
operationDuration, err := meter.Float64Histogram(
119-
"db.client.operation.duration",
118+
var operationDurationOpts []metric.Float64HistogramOption
119+
operationDurationOpts = append(operationDurationOpts,
120120
metric.WithDescription("Duration of database client operations"),
121121
metric.WithUnit("s"),
122-
metric.WithExplicitBucketBoundaries(cfg.histogramBuckets...),
122+
)
123+
if cfg.histAggregation == HistogramAggregationExplicitBucket {
124+
operationDurationOpts = append(operationDurationOpts,
125+
metric.WithExplicitBucketBoundaries(cfg.bucketsOperationDuration...),
126+
)
127+
}
128+
operationDuration, err := meter.Float64Histogram(
129+
"db.client.operation.duration",
130+
operationDurationOpts...,
123131
)
124132
if err != nil {
125133
return fmt.Errorf("failed to create operation duration histogram: %w", err)
@@ -136,11 +144,20 @@ func initOnce(client redis.UniversalClient, opts ...Option) error {
136144
}
137145

138146
// Create histogram for connection creation time
139-
connectionCreateTime, err := meter.Float64Histogram(
140-
"db.client.connection.create_time",
147+
var connectionCreateTimeOpts []metric.Float64HistogramOption
148+
connectionCreateTimeOpts = append(connectionCreateTimeOpts,
141149
metric.WithDescription("The time it took to create a new connection"),
142150
metric.WithUnit("s"),
143151
)
152+
if cfg.histAggregation == HistogramAggregationExplicitBucket {
153+
connectionCreateTimeOpts = append(connectionCreateTimeOpts,
154+
metric.WithExplicitBucketBoundaries(cfg.bucketsConnectionCreateTime...),
155+
)
156+
}
157+
connectionCreateTime, err := meter.Float64Histogram(
158+
"db.client.connection.create_time",
159+
connectionCreateTimeOpts...,
160+
)
144161
if err != nil {
145162
return fmt.Errorf("failed to create connection create time histogram: %w", err)
146163
}

internal/pool/pool.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,11 @@ func (p *ConnPool) newConn(ctx context.Context, pooled bool) (*Conn, error) {
389389
p.poolSize.Add(1)
390390
}
391391
}
392-
connectionStateChangeCallback(ctx, cn, "", "idle")
392+
393+
// Notify metrics: new connection created and idle
394+
if connectionStateChangeCallback != nil {
395+
connectionStateChangeCallback(ctx, cn, "", "idle")
396+
}
393397

394398
return cn, nil
395399
}

otel.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package redis
22

33
import (
44
"context"
5-
"fmt"
65
"net"
76
"time"
87

@@ -93,9 +92,6 @@ func (a *otelRecorderAdapter) RecordConnectionStateChange(ctx context.Context, c
9392
}
9493

9594
func (a *otelRecorderAdapter) RecordConnectionCreateTime(ctx context.Context, duration time.Duration, cn *pool.Conn) {
96-
97-
fmt.Println("RecordConnectionCreateTime---")
98-
9995
// Convert internal pool.Conn to public ConnInfo
10096
var connInfo ConnInfo
10197
if cn != nil {

0 commit comments

Comments
 (0)