github.com/thanos-io/thanos@v0.32.5/pkg/queryfrontend/downsampled.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package queryfrontend 5 6 import ( 7 "context" 8 "math" 9 10 "github.com/prometheus/client_golang/prometheus" 11 "github.com/prometheus/client_golang/prometheus/promauto" 12 13 "github.com/thanos-io/thanos/internal/cortex/querier/queryrange" 14 "github.com/thanos-io/thanos/pkg/compact/downsample" 15 ) 16 17 // DownsampledMiddleware creates a new Middleware that requests downsampled data 18 // should response to original request with auto max_source_resolution not contain data points. 19 func DownsampledMiddleware(merger queryrange.Merger, registerer prometheus.Registerer) queryrange.Middleware { 20 return queryrange.MiddlewareFunc(func(next queryrange.Handler) queryrange.Handler { 21 return downsampled{ 22 next: next, 23 merger: merger, 24 additionalQueriesCount: promauto.With(registerer).NewCounter(prometheus.CounterOpts{ 25 Namespace: "thanos", 26 Name: "frontend_downsampled_extra_queries_total", 27 Help: "Total number of additional queries for downsampled data", 28 }), 29 } 30 }) 31 } 32 33 type downsampled struct { 34 next queryrange.Handler 35 merger queryrange.Merger 36 37 // Metrics. 38 additionalQueriesCount prometheus.Counter 39 } 40 41 var resolutions = []int64{downsample.ResLevel1, downsample.ResLevel2} 42 43 func (d downsampled) Do(ctx context.Context, req queryrange.Request) (queryrange.Response, error) { 44 tqrr, ok := req.(*ThanosQueryRangeRequest) 45 if !ok || !tqrr.AutoDownsampling { 46 return d.next.Do(ctx, req) 47 } 48 49 var ( 50 resps = make([]queryrange.Response, 0) 51 resp queryrange.Response 52 err error 53 i int 54 ) 55 56 forLoop: 57 for i < len(resolutions) { 58 if i > 0 { 59 d.additionalQueriesCount.Inc() 60 } 61 r := *tqrr 62 resp, err = d.next.Do(ctx, &r) 63 if err != nil { 64 return nil, err 65 } 66 resps = append(resps, resp) 67 // Set MaxSourceResolution for next request, if any. 68 for i < len(resolutions) { 69 if tqrr.MaxSourceResolution < resolutions[i] { 70 tqrr.AutoDownsampling = false 71 tqrr.MaxSourceResolution = resolutions[i] 72 break 73 } 74 i++ 75 } 76 m := minResponseTime(resp) 77 switch m { 78 case tqrr.Start: // Response not impacted by retention policy. 79 break forLoop 80 case -1: // Empty response, retry with higher MaxSourceResolution. 81 continue 82 default: // Data partially present, query for empty part with higher MaxSourceResolution. 83 tqrr.End = m - tqrr.Step 84 } 85 if tqrr.Start > tqrr.End { 86 break forLoop 87 } 88 } 89 response, err := d.merger.MergeResponse(req, resps...) 90 if err != nil { 91 return nil, err 92 } 93 return response, nil 94 } 95 96 // minResponseTime returns earliest timestamp in r.Data.Result. 97 // -1 is returned if r contains no data points. 98 // Each SampleStream within r.Data.Result must be sorted by timestamp. 99 func minResponseTime(r queryrange.Response) int64 { 100 var res = r.(*queryrange.PrometheusResponse).Data.Result 101 if len(res) == 0 || (len(res[0].Samples) == 0 && len(res[0].Histograms) == 0) { 102 return -1 103 } 104 105 minTs := int64(math.MaxInt64) 106 107 for _, sampleStream := range res { 108 if len(sampleStream.Samples) > 0 { 109 if ts := sampleStream.Samples[0].TimestampMs; ts < minTs { 110 minTs = ts 111 } 112 } 113 114 if len(sampleStream.Histograms) > 0 { 115 if ts := sampleStream.Histograms[0].Timestamp; ts < minTs { 116 minTs = ts 117 } 118 } 119 } 120 121 return minTs 122 }