github.com/thanos-io/thanos@v0.32.5/pkg/queryfrontend/split_by_interval.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 // This is a modified copy from 5 // https://github.com/cortexproject/cortex/blob/master/pkg/querier/queryrange/split_by_interval.go. 6 7 package queryfrontend 8 9 import ( 10 "context" 11 "time" 12 13 "github.com/prometheus/client_golang/prometheus" 14 "github.com/prometheus/client_golang/prometheus/promauto" 15 16 "github.com/thanos-io/thanos/internal/cortex/querier/queryrange" 17 ) 18 19 // SplitByIntervalMiddleware creates a new Middleware that splits requests by a given interval. 20 func SplitByIntervalMiddleware(interval queryrange.IntervalFn, limits queryrange.Limits, merger queryrange.Merger, registerer prometheus.Registerer) queryrange.Middleware { 21 return queryrange.MiddlewareFunc(func(next queryrange.Handler) queryrange.Handler { 22 return splitByInterval{ 23 next: next, 24 limits: limits, 25 merger: merger, 26 interval: interval, 27 splitByCounter: promauto.With(registerer).NewCounter(prometheus.CounterOpts{ 28 Namespace: "thanos", 29 Name: "frontend_split_queries_total", 30 Help: "Total number of underlying query requests after the split by interval is applied", 31 }), 32 } 33 }) 34 } 35 36 type splitByInterval struct { 37 next queryrange.Handler 38 limits queryrange.Limits 39 merger queryrange.Merger 40 interval queryrange.IntervalFn 41 42 // Metrics. 43 splitByCounter prometheus.Counter 44 } 45 46 func (s splitByInterval) Do(ctx context.Context, r queryrange.Request) (queryrange.Response, error) { 47 // First we're going to build new requests, one for each day, taking care 48 // to line up the boundaries with step. 49 reqs, err := splitQuery(r, s.interval(r)) 50 if err != nil { 51 return nil, err 52 } 53 s.splitByCounter.Add(float64(len(reqs))) 54 55 reqResps, err := queryrange.DoRequests(ctx, s.next, reqs, s.limits) 56 if err != nil { 57 return nil, err 58 } 59 60 resps := make([]queryrange.Response, 0, len(reqResps)) 61 for _, reqResp := range reqResps { 62 resps = append(resps, reqResp.Response) 63 } 64 65 response, err := s.merger.MergeResponse(r, resps...) 66 if err != nil { 67 return nil, err 68 } 69 return response, nil 70 } 71 72 func splitQuery(r queryrange.Request, interval time.Duration) ([]queryrange.Request, error) { 73 var reqs []queryrange.Request 74 if _, ok := r.(*ThanosQueryRangeRequest); ok { 75 // Replace @ modifier function to their respective constant values in the query. 76 // This way subqueries will be evaluated at the same time as the parent query. 77 query, err := queryrange.EvaluateAtModifierFunction(r.GetQuery(), r.GetStart(), r.GetEnd()) 78 if err != nil { 79 return nil, err 80 } 81 if start := r.GetStart(); start == r.GetEnd() { 82 reqs = append(reqs, r.WithStartEnd(start, start)) 83 } else { 84 for ; start < r.GetEnd(); start = nextIntervalBoundary(start, r.GetStep(), interval) + r.GetStep() { 85 end := nextIntervalBoundary(start, r.GetStep(), interval) 86 if end+r.GetStep() >= r.GetEnd() { 87 end = r.GetEnd() 88 } 89 90 reqs = append(reqs, r.WithQuery(query).WithStartEnd(start, end)) 91 } 92 } 93 } else { 94 dur := int64(interval / time.Millisecond) 95 for start := r.GetStart(); start < r.GetEnd(); start = start + dur { 96 end := start + dur 97 if end > r.GetEnd() { 98 end = r.GetEnd() 99 } 100 101 reqs = append(reqs, r.WithStartEnd(start, end)) 102 } 103 } 104 105 return reqs, nil 106 } 107 108 // Round up to the step before the next interval boundary. 109 func nextIntervalBoundary(t, step int64, interval time.Duration) int64 { 110 msPerInterval := int64(interval / time.Millisecond) 111 startOfNextInterval := ((t / msPerInterval) + 1) * msPerInterval 112 // ensure that target is a multiple of steps away from the start time 113 target := startOfNextInterval - ((startOfNextInterval - t) % step) 114 if target == startOfNextInterval { 115 target -= step 116 } 117 return target 118 }