104 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			104 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
| 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/redis@v6.15.9+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)
 | |
| 	}
 | |
| }
 |