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 }