...

Source file src/github.com/letsencrypt/boulder/redis/metrics.go

Documentation: github.com/letsencrypt/boulder/redis

     1  package redis
     2  
     3  import (
     4  	"errors"
     5  	"slices"
     6  	"strings"
     7  
     8  	"github.com/prometheus/client_golang/prometheus"
     9  	"github.com/redis/go-redis/v9"
    10  )
    11  
    12  // An interface satisfied by *redis.ClusterClient and also by a mock in our tests.
    13  type poolStatGetter interface {
    14  	PoolStats() *redis.PoolStats
    15  }
    16  
    17  var _ poolStatGetter = (*redis.ClusterClient)(nil)
    18  
    19  type metricsCollector struct {
    20  	statGetter poolStatGetter
    21  
    22  	// Stats accessible from the go-redis connector:
    23  	// https://pkg.go.dev/github.com/go-redis/redis@v6.15.9+incompatible/internal/pool#Stats
    24  	lookups    *prometheus.Desc
    25  	totalConns *prometheus.Desc
    26  	idleConns  *prometheus.Desc
    27  	staleConns *prometheus.Desc
    28  }
    29  
    30  // Describe is implemented with DescribeByCollect. That's possible because the
    31  // Collect method will always return the same metrics with the same descriptors.
    32  func (dbc metricsCollector) Describe(ch chan<- *prometheus.Desc) {
    33  	prometheus.DescribeByCollect(dbc, ch)
    34  }
    35  
    36  // Collect first triggers the Redis ClusterClient's PoolStats function.
    37  // Then it creates constant metrics for each Stats value on the fly based
    38  // on the returned data.
    39  //
    40  // Note that Collect could be called concurrently, so we depend on PoolStats()
    41  // to be concurrency-safe.
    42  func (dbc metricsCollector) Collect(ch chan<- prometheus.Metric) {
    43  	writeGauge := func(stat *prometheus.Desc, val uint32, labelValues ...string) {
    44  		ch <- prometheus.MustNewConstMetric(stat, prometheus.GaugeValue, float64(val), labelValues...)
    45  	}
    46  
    47  	stats := dbc.statGetter.PoolStats()
    48  	writeGauge(dbc.lookups, stats.Hits, "hit")
    49  	writeGauge(dbc.lookups, stats.Misses, "miss")
    50  	writeGauge(dbc.lookups, stats.Timeouts, "timeout")
    51  	writeGauge(dbc.totalConns, stats.TotalConns)
    52  	writeGauge(dbc.idleConns, stats.IdleConns)
    53  	writeGauge(dbc.staleConns, stats.StaleConns)
    54  }
    55  
    56  // newClientMetricsCollector is broken out for testing purposes.
    57  func newClientMetricsCollector(statGetter poolStatGetter, labels prometheus.Labels) metricsCollector {
    58  	return metricsCollector{
    59  		statGetter: statGetter,
    60  		lookups: prometheus.NewDesc(
    61  			"redis_connection_pool_lookups",
    62  			"Number of lookups for a connection in the pool, labeled by hit/miss",
    63  			[]string{"result"}, labels),
    64  		totalConns: prometheus.NewDesc(
    65  			"redis_connection_pool_total_conns",
    66  			"Number of total connections in the pool.",
    67  			nil, labels),
    68  		idleConns: prometheus.NewDesc(
    69  			"redis_connection_pool_idle_conns",
    70  			"Number of idle connections in the pool.",
    71  			nil, labels),
    72  		staleConns: prometheus.NewDesc(
    73  			"redis_connection_pool_stale_conns",
    74  			"Number of stale connections removed from the pool.",
    75  			nil, labels),
    76  	}
    77  }
    78  
    79  // MustRegisterClientMetricsCollector registers a metrics collector for the
    80  // given Redis client with the provided prometheus.Registerer. The collector
    81  // will report metrics labelled by the provided addresses and username. If the
    82  // collector is already registered, this function is a no-op.
    83  func MustRegisterClientMetricsCollector(client poolStatGetter, stats prometheus.Registerer, addrs map[string]string, user string) {
    84  	var labelAddrs []string
    85  	for addr := range addrs {
    86  		labelAddrs = append(labelAddrs, addr)
    87  	}
    88  	// Keep the list of addresses sorted for consistency.
    89  	slices.Sort(labelAddrs)
    90  	labels := prometheus.Labels{
    91  		"addresses": strings.Join(labelAddrs, ", "),
    92  		"user":      user,
    93  	}
    94  	err := stats.Register(newClientMetricsCollector(client, labels))
    95  	if err != nil {
    96  		are := prometheus.AlreadyRegisteredError{}
    97  		if errors.As(err, &are) {
    98  			// The collector is already registered using the same labels.
    99  			return
   100  		}
   101  		panic(err)
   102  	}
   103  }
   104  

View as plain text