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  }