4
4
"context"
5
5
"fmt"
6
6
"net"
7
+ "sync"
7
8
"time"
8
9
9
10
"go.opentelemetry.io/otel"
@@ -13,6 +14,12 @@ import (
13
14
"github.com/redis/go-redis/v9"
14
15
)
15
16
17
+ type metricsState struct {
18
+ registrations []metric.Registration
19
+ closed bool
20
+ mutex sync.Mutex
21
+ }
22
+
16
23
// InstrumentMetrics starts reporting OpenTelemetry Metrics.
17
24
//
18
25
// Based on https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/database-metrics.md
@@ -30,49 +37,42 @@ func InstrumentMetrics(rdb redis.UniversalClient, opts ...MetricsOption) error {
30
37
)
31
38
}
32
39
33
- switch rdb := rdb .(type ) {
34
- case * redis.Client :
35
- if conf .poolName == "" {
36
- opt := rdb .Options ()
37
- conf .poolName = opt .Addr
40
+ var state * metricsState
41
+ if conf .closeChan != nil {
42
+ state = & metricsState {
43
+ registrations : make ([]metric.Registration , 0 ),
44
+ closed : false ,
45
+ mutex : sync.Mutex {},
38
46
}
39
- conf .attrs = append (conf .attrs , attribute .String ("pool.name" , conf .poolName ))
40
47
41
- if err := reportPoolStats (rdb , conf ); err != nil {
42
- return err
43
- }
44
- if err := addMetricsHook (rdb , conf ); err != nil {
45
- return err
46
- }
47
- return nil
48
- case * redis.ClusterClient :
49
- rdb .OnNewNode (func (rdb * redis.Client ) {
50
- if conf .poolName == "" {
51
- opt := rdb .Options ()
52
- conf .poolName = opt .Addr
53
- }
54
- conf .attrs = append (conf .attrs , attribute .String ("pool.name" , conf .poolName ))
48
+ go func () {
49
+ <- conf .closeChan
55
50
56
- if err := reportPoolStats (rdb , conf ); err != nil {
57
- otel .Handle (err )
51
+ state .mutex .Lock ()
52
+ state .closed = true
53
+
54
+ for _ , registration := range state .registrations {
55
+ if err := registration .Unregister (); err != nil {
56
+ otel .Handle (err )
57
+ }
58
58
}
59
- if err := addMetricsHook (rdb , conf ); err != nil {
59
+ state .mutex .Unlock ()
60
+ }()
61
+ }
62
+
63
+ switch rdb := rdb .(type ) {
64
+ case * redis.Client :
65
+ return registerClient (rdb , conf , state )
66
+ case * redis.ClusterClient :
67
+ rdb .OnNewNode (func (rdb * redis.Client ) {
68
+ if err := registerClient (rdb , conf , state ); err != nil {
60
69
otel .Handle (err )
61
70
}
62
71
})
63
72
return nil
64
73
case * redis.Ring :
65
74
rdb .OnNewNode (func (rdb * redis.Client ) {
66
- if conf .poolName == "" {
67
- opt := rdb .Options ()
68
- conf .poolName = opt .Addr
69
- }
70
- conf .attrs = append (conf .attrs , attribute .String ("pool.name" , conf .poolName ))
71
-
72
- if err := reportPoolStats (rdb , conf ); err != nil {
73
- otel .Handle (err )
74
- }
75
- if err := addMetricsHook (rdb , conf ); err != nil {
75
+ if err := registerClient (rdb , conf , state ); err != nil {
76
76
otel .Handle (err )
77
77
}
78
78
})
@@ -82,7 +82,38 @@ func InstrumentMetrics(rdb redis.UniversalClient, opts ...MetricsOption) error {
82
82
}
83
83
}
84
84
85
- func reportPoolStats (rdb * redis.Client , conf * config ) error {
85
+ func registerClient (rdb * redis.Client , conf * config , state * metricsState ) error {
86
+ if state != nil {
87
+ state .mutex .Lock ()
88
+ defer state .mutex .Unlock ()
89
+
90
+ if state .closed {
91
+ return nil
92
+ }
93
+ }
94
+
95
+ if conf .poolName == "" {
96
+ opt := rdb .Options ()
97
+ conf .poolName = opt .Addr
98
+ }
99
+ conf .attrs = append (conf .attrs , attribute .String ("pool.name" , conf .poolName ))
100
+
101
+ registration , err := reportPoolStats (rdb , conf )
102
+ if err != nil {
103
+ return err
104
+ }
105
+
106
+ if state != nil {
107
+ state .registrations = append (state .registrations , registration )
108
+ }
109
+
110
+ if err := addMetricsHook (rdb , conf ); err != nil {
111
+ return err
112
+ }
113
+ return nil
114
+ }
115
+
116
+ func reportPoolStats (rdb * redis.Client , conf * config ) (metric.Registration , error ) {
86
117
labels := conf .attrs
87
118
idleAttrs := append (labels , attribute .String ("state" , "idle" ))
88
119
usedAttrs := append (labels , attribute .String ("state" , "used" ))
@@ -92,59 +123,59 @@ func reportPoolStats(rdb *redis.Client, conf *config) error {
92
123
metric .WithDescription ("The maximum number of idle open connections allowed" ),
93
124
)
94
125
if err != nil {
95
- return err
126
+ return nil , err
96
127
}
97
128
98
129
idleMin , err := conf .meter .Int64ObservableUpDownCounter (
99
130
"db.client.connections.idle.min" ,
100
131
metric .WithDescription ("The minimum number of idle open connections allowed" ),
101
132
)
102
133
if err != nil {
103
- return err
134
+ return nil , err
104
135
}
105
136
106
137
connsMax , err := conf .meter .Int64ObservableUpDownCounter (
107
138
"db.client.connections.max" ,
108
139
metric .WithDescription ("The maximum number of open connections allowed" ),
109
140
)
110
141
if err != nil {
111
- return err
142
+ return nil , err
112
143
}
113
144
114
145
usage , err := conf .meter .Int64ObservableUpDownCounter (
115
146
"db.client.connections.usage" ,
116
147
metric .WithDescription ("The number of connections that are currently in state described by the state attribute" ),
117
148
)
118
149
if err != nil {
119
- return err
150
+ return nil , err
120
151
}
121
152
122
153
timeouts , err := conf .meter .Int64ObservableUpDownCounter (
123
154
"db.client.connections.timeouts" ,
124
155
metric .WithDescription ("The number of connection timeouts that have occurred trying to obtain a connection from the pool" ),
125
156
)
126
157
if err != nil {
127
- return err
158
+ return nil , err
128
159
}
129
160
130
161
hits , err := conf .meter .Int64ObservableUpDownCounter (
131
162
"db.client.connections.hits" ,
132
163
metric .WithDescription ("The number of times free connection was found in the pool" ),
133
164
)
134
165
if err != nil {
135
- return err
166
+ return nil , err
136
167
}
137
168
138
169
misses , err := conf .meter .Int64ObservableUpDownCounter (
139
170
"db.client.connections.misses" ,
140
171
metric .WithDescription ("The number of times free connection was not found in the pool" ),
141
172
)
142
173
if err != nil {
143
- return err
174
+ return nil , err
144
175
}
145
176
146
177
redisConf := rdb .Options ()
147
- _ , err = conf .meter .RegisterCallback (
178
+ return conf .meter .RegisterCallback (
148
179
func (ctx context.Context , o metric.Observer ) error {
149
180
stats := rdb .PoolStats ()
150
181
@@ -168,8 +199,6 @@ func reportPoolStats(rdb *redis.Client, conf *config) error {
168
199
hits ,
169
200
misses ,
170
201
)
171
-
172
- return err
173
202
}
174
203
175
204
func addMetricsHook (rdb * redis.Client , conf * config ) error {
0 commit comments