github.com/thanos-io/thanos@v0.32.5/pkg/store/telemetry.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package store
     5  
     6  import (
     7  	"sort"
     8  	"strconv"
     9  
    10  	"github.com/prometheus/client_golang/prometheus"
    11  	"github.com/prometheus/client_golang/prometheus/promauto"
    12  
    13  	"github.com/thanos-io/thanos/pkg/store/storepb"
    14  )
    15  
    16  // seriesStatsAggregator aggregates results from fanned-out queries into a histogram given their
    17  // response's shape.
    18  type seriesStatsAggregator struct {
    19  	queryDuration    *prometheus.HistogramVec
    20  	seriesLeBuckets  []float64
    21  	samplesLeBuckets []float64
    22  
    23  	seriesStats storepb.SeriesStatsCounter
    24  }
    25  
    26  type seriesStatsAggregatorFactory struct {
    27  	queryDuration    *prometheus.HistogramVec
    28  	seriesLeBuckets  []float64
    29  	samplesLeBuckets []float64
    30  }
    31  
    32  func (f *seriesStatsAggregatorFactory) NewAggregator() SeriesQueryPerformanceMetricsAggregator {
    33  	return &seriesStatsAggregator{
    34  		queryDuration:    f.queryDuration,
    35  		seriesLeBuckets:  f.seriesLeBuckets,
    36  		samplesLeBuckets: f.samplesLeBuckets,
    37  		seriesStats:      storepb.SeriesStatsCounter{},
    38  	}
    39  }
    40  
    41  func NewSeriesStatsAggregatorFactory(
    42  	reg prometheus.Registerer,
    43  	durationQuantiles []float64,
    44  	sampleQuantiles []float64,
    45  	seriesQuantiles []float64,
    46  ) *seriesStatsAggregatorFactory {
    47  	return &seriesStatsAggregatorFactory{
    48  		queryDuration: promauto.With(reg).NewHistogramVec(prometheus.HistogramOpts{
    49  			Name:    "thanos_store_api_query_duration_seconds",
    50  			Help:    "Duration of the Thanos Store API select phase for a query.",
    51  			Buckets: durationQuantiles,
    52  		}, []string{"series_le", "samples_le"}),
    53  		seriesLeBuckets:  seriesQuantiles,
    54  		samplesLeBuckets: sampleQuantiles,
    55  	}
    56  }
    57  
    58  // NewSeriesStatsAggregator is a constructor for seriesStatsAggregator.
    59  func NewSeriesStatsAggregator(
    60  	reg prometheus.Registerer,
    61  	durationQuantiles []float64,
    62  	sampleQuantiles []float64,
    63  	seriesQuantiles []float64,
    64  ) *seriesStatsAggregator {
    65  	return &seriesStatsAggregator{
    66  		queryDuration: promauto.With(reg).NewHistogramVec(prometheus.HistogramOpts{
    67  			Name:    "thanos_store_api_query_duration_seconds",
    68  			Help:    "Duration of the Thanos Store API select phase for a query.",
    69  			Buckets: durationQuantiles,
    70  		}, []string{"series_le", "samples_le"}),
    71  		seriesLeBuckets:  seriesQuantiles,
    72  		samplesLeBuckets: sampleQuantiles,
    73  		seriesStats:      storepb.SeriesStatsCounter{},
    74  	}
    75  }
    76  
    77  // Aggregate is an aggregator for merging `storepb.SeriesStatsCounter` for each incoming fanned out query.
    78  func (s *seriesStatsAggregator) Aggregate(stats storepb.SeriesStatsCounter) {
    79  	s.seriesStats.Series += stats.Series
    80  	s.seriesStats.Samples += stats.Samples
    81  	s.seriesStats.Chunks += stats.Chunks
    82  }
    83  
    84  // Observe commits the aggregated SeriesStatsCounter as an observation.
    85  func (s *seriesStatsAggregator) Observe(duration float64) {
    86  	if s.seriesStats.Series == 0 || s.seriesStats.Samples == 0 || s.seriesStats.Chunks == 0 {
    87  		return
    88  	}
    89  	// Bucket matching for series/labels matchSeriesBucket/matchSamplesBucket => float64, float64
    90  	seriesLeBucket := findBucket(float64(s.seriesStats.Series), s.seriesLeBuckets)
    91  	samplesLeBucket := findBucket(float64(s.seriesStats.Samples), s.samplesLeBuckets)
    92  	s.queryDuration.With(prometheus.Labels{
    93  		"series_le":  seriesLeBucket,
    94  		"samples_le": samplesLeBucket,
    95  	}).Observe(duration)
    96  	s.reset()
    97  }
    98  
    99  func (s *seriesStatsAggregator) reset() {
   100  	s.seriesStats = storepb.SeriesStatsCounter{}
   101  }
   102  
   103  func findBucket(value float64, quantiles []float64) string {
   104  	if len(quantiles) == 0 {
   105  		return "+Inf"
   106  	}
   107  
   108  	// If the value is bigger than the largest bucket we return +Inf
   109  	if value >= float64(quantiles[len(quantiles)-1]) {
   110  		return "+Inf"
   111  	}
   112  
   113  	// SearchFloats64s gets the appropriate index in the quantiles array based on the value
   114  	return strconv.FormatFloat(quantiles[sort.SearchFloat64s(quantiles, value)], 'f', -1, 64)
   115  }
   116  
   117  type SeriesQueryPerformanceMetricsAggregatorFactory interface {
   118  	NewAggregator() SeriesQueryPerformanceMetricsAggregator
   119  }
   120  
   121  type SeriesQueryPerformanceMetricsAggregator interface {
   122  	Aggregate(seriesStats storepb.SeriesStatsCounter)
   123  	Observe(duration float64)
   124  }
   125  
   126  // NoopSeriesStatsAggregator is a query performance series aggregator that does nothing.
   127  type NoopSeriesStatsAggregator struct{}
   128  
   129  func (s *NoopSeriesStatsAggregator) Aggregate(_ storepb.SeriesStatsCounter) {}
   130  
   131  func (s *NoopSeriesStatsAggregator) Observe(_ float64) {}
   132  
   133  // NoopSeriesStatsAggregatorFactory is a query performance series aggregator factory that does nothing.
   134  type NoopSeriesStatsAggregatorFactory struct{}
   135  
   136  func (s *NoopSeriesStatsAggregatorFactory) NewAggregator() SeriesQueryPerformanceMetricsAggregator {
   137  	return &NoopSeriesStatsAggregator{}
   138  }
   139  
   140  // instrumentedStoreServer is a storepb.StoreServer that exposes metrics about Series requests.
   141  type instrumentedStoreServer struct {
   142  	storepb.StoreServer
   143  	seriesRequested prometheus.Histogram
   144  	chunksRequested prometheus.Histogram
   145  }
   146  
   147  // NewInstrumentedStoreServer creates a new instrumentedStoreServer.
   148  func NewInstrumentedStoreServer(reg prometheus.Registerer, store storepb.StoreServer) storepb.StoreServer {
   149  	return &instrumentedStoreServer{
   150  		StoreServer: store,
   151  		seriesRequested: promauto.With(reg).NewHistogram(prometheus.HistogramOpts{
   152  			Name:    "thanos_store_server_series_requested",
   153  			Help:    "Number of requested series for Series calls",
   154  			Buckets: []float64{1, 10, 100, 1000, 10000, 100000, 1000000},
   155  		}),
   156  		chunksRequested: promauto.With(reg).NewHistogram(prometheus.HistogramOpts{
   157  			Name:    "thanos_store_server_chunks_requested",
   158  			Help:    "Number of requested chunks for Series calls",
   159  			Buckets: []float64{1, 100, 1000, 10000, 100000, 10000000, 100000000, 1000000000},
   160  		}),
   161  	}
   162  }
   163  
   164  func (s *instrumentedStoreServer) Series(req *storepb.SeriesRequest, srv storepb.Store_SeriesServer) error {
   165  	instrumented := newInstrumentedServer(srv)
   166  	if err := s.StoreServer.Series(req, instrumented); err != nil {
   167  		return err
   168  	}
   169  
   170  	s.seriesRequested.Observe(instrumented.seriesSent)
   171  	s.chunksRequested.Observe(instrumented.chunksSent)
   172  	return nil
   173  }
   174  
   175  // instrumentedServer is a storepb.Store_SeriesServer that tracks statistics about sent series.
   176  type instrumentedServer struct {
   177  	storepb.Store_SeriesServer
   178  	seriesSent float64
   179  	chunksSent float64
   180  }
   181  
   182  func newInstrumentedServer(upstream storepb.Store_SeriesServer) *instrumentedServer {
   183  	return &instrumentedServer{Store_SeriesServer: upstream}
   184  }
   185  
   186  func (i *instrumentedServer) Send(response *storepb.SeriesResponse) error {
   187  	if err := i.Store_SeriesServer.Send(response); err != nil {
   188  		return err
   189  	}
   190  	if series := response.GetSeries(); series != nil {
   191  		i.seriesSent++
   192  		i.chunksSent += float64(len(series.Chunks))
   193  	}
   194  	return nil
   195  }