-
-
Notifications
You must be signed in to change notification settings - Fork 613
/
Copy pathmetrics.go
103 lines (92 loc) · 3.47 KB
/
metrics.go
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
package redis
import (
"errors"
"slices"
"strings"
"github.com/prometheus/client_golang/prometheus"
"github.com/redis/go-redis/v9"
)
// An interface satisfied by *redis.ClusterClient and also by a mock in our tests.
type poolStatGetter interface {
PoolStats() *redis.PoolStats
}
var _ poolStatGetter = (*redis.ClusterClient)(nil)
type metricsCollector struct {
statGetter poolStatGetter
// Stats accessible from the go-redis connector:
// https://pkg.go.dev/github.com/go-redis/[email protected]+incompatible/internal/pool#Stats
lookups *prometheus.Desc
totalConns *prometheus.Desc
idleConns *prometheus.Desc
staleConns *prometheus.Desc
}
// Describe is implemented with DescribeByCollect. That's possible because the
// Collect method will always return the same metrics with the same descriptors.
func (dbc metricsCollector) Describe(ch chan<- *prometheus.Desc) {
prometheus.DescribeByCollect(dbc, ch)
}
// Collect first triggers the Redis ClusterClient's PoolStats function.
// Then it creates constant metrics for each Stats value on the fly based
// on the returned data.
//
// Note that Collect could be called concurrently, so we depend on PoolStats()
// to be concurrency-safe.
func (dbc metricsCollector) Collect(ch chan<- prometheus.Metric) {
writeGauge := func(stat *prometheus.Desc, val uint32, labelValues ...string) {
ch <- prometheus.MustNewConstMetric(stat, prometheus.GaugeValue, float64(val), labelValues...)
}
stats := dbc.statGetter.PoolStats()
writeGauge(dbc.lookups, stats.Hits, "hit")
writeGauge(dbc.lookups, stats.Misses, "miss")
writeGauge(dbc.lookups, stats.Timeouts, "timeout")
writeGauge(dbc.totalConns, stats.TotalConns)
writeGauge(dbc.idleConns, stats.IdleConns)
writeGauge(dbc.staleConns, stats.StaleConns)
}
// newClientMetricsCollector is broken out for testing purposes.
func newClientMetricsCollector(statGetter poolStatGetter, labels prometheus.Labels) metricsCollector {
return metricsCollector{
statGetter: statGetter,
lookups: prometheus.NewDesc(
"redis_connection_pool_lookups",
"Number of lookups for a connection in the pool, labeled by hit/miss",
[]string{"result"}, labels),
totalConns: prometheus.NewDesc(
"redis_connection_pool_total_conns",
"Number of total connections in the pool.",
nil, labels),
idleConns: prometheus.NewDesc(
"redis_connection_pool_idle_conns",
"Number of idle connections in the pool.",
nil, labels),
staleConns: prometheus.NewDesc(
"redis_connection_pool_stale_conns",
"Number of stale connections removed from the pool.",
nil, labels),
}
}
// MustRegisterClientMetricsCollector registers a metrics collector for the
// given Redis client with the provided prometheus.Registerer. The collector
// will report metrics labelled by the provided addresses and username. If the
// collector is already registered, this function is a no-op.
func MustRegisterClientMetricsCollector(client poolStatGetter, stats prometheus.Registerer, addrs map[string]string, user string) {
var labelAddrs []string
for addr := range addrs {
labelAddrs = append(labelAddrs, addr)
}
// Keep the list of addresses sorted for consistency.
slices.Sort(labelAddrs)
labels := prometheus.Labels{
"addresses": strings.Join(labelAddrs, ", "),
"user": user,
}
err := stats.Register(newClientMetricsCollector(client, labels))
if err != nil {
are := prometheus.AlreadyRegisteredError{}
if errors.As(err, &are) {
// The collector is already registered using the same labels.
return
}
panic(err)
}
}