registry/internal/metrics/redis/redis.go (149 lines of code) (raw):

package redis import ( "github.com/docker/distribution/metrics" "github.com/prometheus/client_golang/prometheus" "github.com/redis/go-redis/v9" redisprom "github.com/trim21/go-redis-prometheus" ) const ( // Names for the recorded metrics. hitsName = "pool_stats_hits" missesName = "pool_stats_misses" timeoutsName = "pool_stats_timeouts" totalConnsName = "pool_stats_total_conns" idleConnsName = "pool_stats_idle_conns" staleConnsName = "pool_stats_stale_conns" maxConnsName = "pool_stats_max_conns" // Descriptions for the recorded metrics. hitsDesc = "The number of times a free connection was found in the pool." missesDesc = "The number of times a free connection was not found in the pool." timeoutsDesc = "The number of times a wait timeout occurred." totalConnsDesc = "The total number of connections in the pool." idleConnsDesc = "The number of idle connections in the pool." staleConnsDesc = "The number of stale connections removed from the pool." maxConnsDesc = "The maximum number of connections in the pool." subSystem = "redis" defaultInstanceName = "unnamed" ) // PoolStatsGetter describes a getter for *redis.PoolStats. type PoolStatsGetter interface { PoolStats() *redis.PoolStats } var _ PoolStatsGetter = (*redis.Client)(nil) // Options represents options to customize the exported metrics. type Options struct { InstanceName string MaxConns int } // Option is a functional option to customize defaults. type Option func(*Options) // defaultOptions returns the default options. func defaultOptions() *Options { return &Options{ InstanceName: defaultInstanceName, } } func (options *Options) merge(opts ...Option) { for _, opt := range opts { opt(options) } } // WithInstanceName sets the name of the Redis instance. func WithInstanceName(name string) Option { return func(options *Options) { options.InstanceName = name } } // WithMaxConns enables a gauge metric to report the size of the Redis connection pool. This cannot be automatically // detected as all other pool metrics because redis.PoolStats does not expose such attribute. Use this if you need to // monitor the pool saturation. func WithMaxConns(n int) Option { return func(options *Options) { options.MaxConns = n } } // poolStatsCollector is a Prometheus collector for Redis connection pool statuses. type poolStatsCollector struct { client PoolStatsGetter options *Options hitsDesc *prometheus.Desc missesDesc *prometheus.Desc timeoutsDesc *prometheus.Desc totalConnsDesc *prometheus.Desc idleConnsDesc *prometheus.Desc staleConnsDesc *prometheus.Desc maxConnsDesc *prometheus.Desc } // Describe implements prometheus.Collector. func (c *poolStatsCollector) Describe(ch chan<- *prometheus.Desc) { ch <- c.hitsDesc ch <- c.missesDesc ch <- c.timeoutsDesc ch <- c.totalConnsDesc ch <- c.idleConnsDesc ch <- c.staleConnsDesc ch <- c.maxConnsDesc } // Collect implements prometheus.Collector. func (c *poolStatsCollector) Collect(ch chan<- prometheus.Metric) { stats := c.client.PoolStats() ch <- prometheus.MustNewConstMetric(c.hitsDesc, prometheus.GaugeValue, float64(stats.Hits)) ch <- prometheus.MustNewConstMetric(c.missesDesc, prometheus.GaugeValue, float64(stats.Misses)) ch <- prometheus.MustNewConstMetric(c.timeoutsDesc, prometheus.GaugeValue, float64(stats.Timeouts)) ch <- prometheus.MustNewConstMetric(c.totalConnsDesc, prometheus.GaugeValue, float64(stats.TotalConns)) ch <- prometheus.MustNewConstMetric(c.idleConnsDesc, prometheus.GaugeValue, float64(stats.IdleConns)) ch <- prometheus.MustNewConstMetric(c.staleConnsDesc, prometheus.GaugeValue, float64(stats.StaleConns)) ch <- prometheus.MustNewConstMetric(c.maxConnsDesc, prometheus.GaugeValue, float64(c.options.MaxConns)) } var _ prometheus.Collector = (*poolStatsCollector)(nil) // NewPoolStatsCollector returns a new Redis pool stats collector that implements prometheus.Collector. func NewPoolStatsCollector(client PoolStatsGetter, opts ...Option) prometheus.Collector { options := defaultOptions() options.merge(opts...) constLabels := prometheus.Labels{"instance": options.InstanceName} return &poolStatsCollector{ options: options, client: client, hitsDesc: prometheus.NewDesc( prometheus.BuildFQName(metrics.NamespacePrefix, subSystem, hitsName), hitsDesc, nil, constLabels, ), missesDesc: prometheus.NewDesc( prometheus.BuildFQName(metrics.NamespacePrefix, subSystem, missesName), missesDesc, nil, constLabels, ), timeoutsDesc: prometheus.NewDesc( prometheus.BuildFQName(metrics.NamespacePrefix, subSystem, timeoutsName), timeoutsDesc, nil, constLabels, ), totalConnsDesc: prometheus.NewDesc( prometheus.BuildFQName(metrics.NamespacePrefix, subSystem, totalConnsName), totalConnsDesc, nil, constLabels, ), idleConnsDesc: prometheus.NewDesc( prometheus.BuildFQName(metrics.NamespacePrefix, subSystem, idleConnsName), idleConnsDesc, nil, constLabels, ), staleConnsDesc: prometheus.NewDesc( prometheus.BuildFQName(metrics.NamespacePrefix, subSystem, staleConnsName), staleConnsDesc, nil, constLabels, ), maxConnsDesc: prometheus.NewDesc( prometheus.BuildFQName(metrics.NamespacePrefix, subSystem, maxConnsName), maxConnsDesc, nil, constLabels, ), } } var buckets = []float64{.001, .005, .01, .025, .05, .1, .25, .5, 1} // InstrumentClient instruments a Redis client with Prometheus metrics for operations and connection pool stats. func InstrumentClient(client redis.UniversalClient, opts ...Option) { options := &Options{} options.merge(opts...) // command metrics, through https://github.com/globocom/go-redis-prometheus hook := redisprom.NewHook( redisprom.WithNamespace(metrics.NamespacePrefix), redisprom.WithInstanceName(options.InstanceName), redisprom.WithDurationBuckets(buckets), ) client.AddHook(hook) // connection pool metrics c := NewPoolStatsCollector(client, opts...) prometheus.MustRegister(c) }