github.com/thanos-io/thanos@v0.32.5/internal/cortex/querier/queryrange/split_by_interval.go (about) 1 // Copyright (c) The Cortex Authors. 2 // Licensed under the Apache License 2.0. 3 4 package queryrange 5 6 import ( 7 "context" 8 "net/http" 9 "time" 10 11 "github.com/prometheus/client_golang/prometheus" 12 "github.com/prometheus/client_golang/prometheus/promauto" 13 "github.com/prometheus/prometheus/promql/parser" 14 "github.com/weaveworks/common/httpgrpc" 15 ) 16 17 type IntervalFn func(r Request) time.Duration 18 19 // SplitByIntervalMiddleware creates a new Middleware that splits requests by a given interval. 20 func SplitByIntervalMiddleware(interval IntervalFn, limits Limits, merger Merger, registerer prometheus.Registerer) Middleware { 21 return MiddlewareFunc(func(next Handler) 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: "cortex", 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 Handler 38 limits Limits 39 merger Merger 40 interval IntervalFn 41 42 // Metrics. 43 splitByCounter prometheus.Counter 44 } 45 46 func (s splitByInterval) Do(ctx context.Context, r Request) (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 := DoRequests(ctx, s.next, reqs, s.limits) 56 if err != nil { 57 return nil, err 58 } 59 60 resps := make([]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 Request, interval time.Duration) ([]Request, error) { 73 // If Start == end we should just run the original request. 74 if r.GetStart() == r.GetEnd() { 75 return []Request{r}, nil 76 } 77 78 // Replace @ modifier function to their respective constant values in the query. 79 // This way subqueries will be evaluated at the same time as the parent query. 80 query, err := EvaluateAtModifierFunction(r.GetQuery(), r.GetStart(), r.GetEnd()) 81 if err != nil { 82 return nil, err 83 } 84 var reqs []Request 85 for start := r.GetStart(); start < r.GetEnd(); start = nextIntervalBoundary(start, r.GetStep(), interval) + r.GetStep() { 86 end := nextIntervalBoundary(start, r.GetStep(), interval) 87 if end+r.GetStep() >= r.GetEnd() { 88 end = r.GetEnd() 89 } 90 91 reqs = append(reqs, r.WithQuery(query).WithStartEnd(start, end)) 92 } 93 return reqs, nil 94 } 95 96 // EvaluateAtModifierFunction parse the query and evaluates the `start()` and `end()` at modifier functions into actual constant timestamps. 97 // For example given the start of the query is 10.00, `http_requests_total[1h] @ start()` query will be replaced with `http_requests_total[1h] @ 10.00` 98 // If the modifier is already a constant, it will be returned as is. 99 func EvaluateAtModifierFunction(query string, start, end int64) (string, error) { 100 expr, err := parser.ParseExpr(query) 101 if err != nil { 102 return "", httpgrpc.Errorf(http.StatusBadRequest, `{"status": "error", "error": "%s"}`, err) 103 } 104 parser.Inspect(expr, func(n parser.Node, _ []parser.Node) error { 105 if selector, ok := n.(*parser.VectorSelector); ok { 106 switch selector.StartOrEnd { 107 case parser.START: 108 selector.Timestamp = &start 109 case parser.END: 110 selector.Timestamp = &end 111 } 112 selector.StartOrEnd = 0 113 } 114 return nil 115 }) 116 return expr.String(), err 117 } 118 119 // Round up to the step before the next interval boundary. 120 func nextIntervalBoundary(t, step int64, interval time.Duration) int64 { 121 msPerInterval := int64(interval / time.Millisecond) 122 startOfNextInterval := ((t / msPerInterval) + 1) * msPerInterval 123 // ensure that target is a multiple of steps away from the start time 124 target := startOfNextInterval - ((startOfNextInterval - t) % step) 125 if target == startOfNextInterval { 126 target -= step 127 } 128 return target 129 }