github.com/thanos-io/thanos@v0.32.5/pkg/queryfrontend/queryrange_codec.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package queryfrontend
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/json"
    10  	"math"
    11  	"net/http"
    12  	"net/url"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/prometheus/common/model"
    18  	"github.com/prometheus/prometheus/model/labels"
    19  	"github.com/prometheus/prometheus/promql/parser"
    20  	"github.com/weaveworks/common/httpgrpc"
    21  
    22  	"github.com/thanos-io/thanos/internal/cortex/querier/queryrange"
    23  	cortexutil "github.com/thanos-io/thanos/internal/cortex/util"
    24  
    25  	queryv1 "github.com/thanos-io/thanos/pkg/api/query"
    26  	"github.com/thanos-io/thanos/pkg/store/storepb"
    27  )
    28  
    29  const (
    30  	// Name of the cache control header.
    31  	cacheControlHeader = "Cache-Control"
    32  
    33  	// Value that cacheControlHeader has if the response indicates that the results should not be cached.
    34  	noStoreValue = "no-store"
    35  )
    36  
    37  var (
    38  	errEndBeforeStart = httpgrpc.Errorf(http.StatusBadRequest, "end timestamp must not be before start time")
    39  	errNegativeStep   = httpgrpc.Errorf(http.StatusBadRequest, "zero or negative query resolution step widths are not accepted. Try a positive integer")
    40  	errStepTooSmall   = httpgrpc.Errorf(http.StatusBadRequest, "exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)")
    41  	errCannotParse    = "cannot parse parameter %s"
    42  )
    43  
    44  // queryRangeCodec is used to encode/decode Thanos query range requests and responses.
    45  type queryRangeCodec struct {
    46  	queryrange.Codec
    47  	partialResponse bool
    48  }
    49  
    50  // NewThanosQueryRangeCodec initializes a queryRangeCodec.
    51  func NewThanosQueryRangeCodec(partialResponse bool) *queryRangeCodec {
    52  	return &queryRangeCodec{
    53  		Codec:           queryrange.PrometheusCodec,
    54  		partialResponse: partialResponse,
    55  	}
    56  }
    57  
    58  func (c queryRangeCodec) DecodeRequest(_ context.Context, r *http.Request, forwardHeaders []string) (queryrange.Request, error) {
    59  	var (
    60  		result ThanosQueryRangeRequest
    61  		err    error
    62  	)
    63  	result.Start, err = cortexutil.ParseTime(r.FormValue("start"))
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	result.End, err = cortexutil.ParseTime(r.FormValue("end"))
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	if result.End < result.Start {
    74  		return nil, errEndBeforeStart
    75  	}
    76  
    77  	result.Step, err = parseDurationMillis(r.FormValue("step"))
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	if result.Step <= 0 {
    83  		return nil, errNegativeStep
    84  	}
    85  
    86  	// For safety, limit the number of returned points per timeseries.
    87  	// This is sufficient for 60s resolution for a week or 1h resolution for a year.
    88  	if (result.End-result.Start)/result.Step > 11000 {
    89  		return nil, errStepTooSmall
    90  	}
    91  
    92  	result.Dedup, err = parseEnableDedupParam(r.FormValue(queryv1.DedupParam))
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	if r.FormValue(queryv1.MaxSourceResolutionParam) == "auto" {
    98  		result.AutoDownsampling = true
    99  		result.MaxSourceResolution = result.Step / 5
   100  	} else {
   101  		result.MaxSourceResolution, err = parseDownsamplingParamMillis(r.FormValue(queryv1.MaxSourceResolutionParam))
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  	}
   106  
   107  	result.PartialResponse, err = parsePartialResponseParam(r.FormValue(queryv1.PartialResponseParam), c.partialResponse)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	if len(r.Form[queryv1.ReplicaLabelsParam]) > 0 {
   113  		result.ReplicaLabels = r.Form[queryv1.ReplicaLabelsParam]
   114  	}
   115  
   116  	result.StoreMatchers, err = parseMatchersParam(r.Form, queryv1.StoreMatcherParam)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	result.ShardInfo, err = parseShardInfo(r.Form, queryv1.ShardInfoParam)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	result.LookbackDelta, err = parseLookbackDelta(r.Form, queryv1.LookbackDeltaParam)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	result.Query = r.FormValue("query")
   132  	result.Explain = r.FormValue(queryv1.QueryExplainParam)
   133  	result.Engine = r.FormValue(queryv1.EngineParam)
   134  	result.Path = r.URL.Path
   135  
   136  	for _, value := range r.Header.Values(cacheControlHeader) {
   137  		if strings.Contains(value, noStoreValue) {
   138  			result.CachingOptions.Disabled = true
   139  			break
   140  		}
   141  	}
   142  
   143  	for _, header := range forwardHeaders {
   144  		for h, hv := range r.Header {
   145  			if strings.EqualFold(h, header) {
   146  				result.Headers = append(result.Headers, &RequestHeader{Name: h, Values: hv})
   147  				break
   148  			}
   149  		}
   150  	}
   151  	return &result, nil
   152  }
   153  
   154  func (c queryRangeCodec) EncodeRequest(ctx context.Context, r queryrange.Request) (*http.Request, error) {
   155  	thanosReq, ok := r.(*ThanosQueryRangeRequest)
   156  	if !ok {
   157  		return nil, httpgrpc.Errorf(http.StatusBadRequest, "invalid request format")
   158  	}
   159  	params := url.Values{
   160  		"start":                      []string{encodeTime(thanosReq.Start)},
   161  		"end":                        []string{encodeTime(thanosReq.End)},
   162  		"step":                       []string{encodeDurationMillis(thanosReq.Step)},
   163  		"query":                      []string{thanosReq.Query},
   164  		queryv1.QueryExplainParam:    []string{thanosReq.Explain},
   165  		queryv1.EngineParam:          []string{thanosReq.Engine},
   166  		queryv1.DedupParam:           []string{strconv.FormatBool(thanosReq.Dedup)},
   167  		queryv1.PartialResponseParam: []string{strconv.FormatBool(thanosReq.PartialResponse)},
   168  		queryv1.ReplicaLabelsParam:   thanosReq.ReplicaLabels,
   169  	}
   170  
   171  	if thanosReq.AutoDownsampling {
   172  		params[queryv1.MaxSourceResolutionParam] = []string{"auto"}
   173  	} else if thanosReq.MaxSourceResolution != 0 {
   174  		// Add this param only if it is set. Set to 0 will impact
   175  		// auto-downsampling in the querier.
   176  		params[queryv1.MaxSourceResolutionParam] = []string{encodeDurationMillis(thanosReq.MaxSourceResolution)}
   177  	}
   178  
   179  	if len(thanosReq.StoreMatchers) > 0 {
   180  		params[queryv1.StoreMatcherParam] = matchersToStringSlice(thanosReq.StoreMatchers)
   181  	}
   182  
   183  	if thanosReq.ShardInfo != nil {
   184  		data, err := encodeShardInfo(thanosReq.ShardInfo)
   185  		if err != nil {
   186  			return nil, err
   187  		}
   188  		params[queryv1.ShardInfoParam] = []string{data}
   189  	}
   190  
   191  	if thanosReq.LookbackDelta > 0 {
   192  		params[queryv1.LookbackDeltaParam] = []string{encodeDurationMillis(thanosReq.LookbackDelta)}
   193  	}
   194  
   195  	req, err := http.NewRequest(http.MethodPost, thanosReq.Path, bytes.NewBufferString(params.Encode()))
   196  	if err != nil {
   197  		return nil, httpgrpc.Errorf(http.StatusBadRequest, "error creating request: %s", err.Error())
   198  	}
   199  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   200  	for _, hv := range thanosReq.Headers {
   201  		for _, v := range hv.Values {
   202  			req.Header.Add(hv.Name, v)
   203  		}
   204  	}
   205  	return req.WithContext(ctx), nil
   206  }
   207  
   208  func parseDurationMillis(s string) (int64, error) {
   209  	if d, err := strconv.ParseFloat(s, 64); err == nil {
   210  		ts := d * float64(time.Second/time.Millisecond)
   211  		if ts > float64(math.MaxInt64) || ts < float64(math.MinInt64) {
   212  			return 0, httpgrpc.Errorf(http.StatusBadRequest, "cannot parse %q to a valid duration. It overflows int64", s)
   213  		}
   214  		return int64(ts), nil
   215  	}
   216  	if d, err := model.ParseDuration(s); err == nil {
   217  		return int64(d) / int64(time.Millisecond/time.Nanosecond), nil
   218  	}
   219  	return 0, httpgrpc.Errorf(http.StatusBadRequest, "cannot parse %q to a valid duration", s)
   220  }
   221  
   222  func parseEnableDedupParam(s string) (bool, error) {
   223  	enableDeduplication := true // Deduplication is enabled by default.
   224  	if s != "" {
   225  		var err error
   226  		enableDeduplication, err = strconv.ParseBool(s)
   227  		if err != nil {
   228  			return enableDeduplication, httpgrpc.Errorf(http.StatusBadRequest, errCannotParse, queryv1.DedupParam)
   229  		}
   230  	}
   231  
   232  	return enableDeduplication, nil
   233  }
   234  
   235  func parseDownsamplingParamMillis(s string) (int64, error) {
   236  	var maxSourceResolution int64
   237  	if s != "" {
   238  		var err error
   239  		maxSourceResolution, err = parseDurationMillis(s)
   240  		if err != nil {
   241  			return maxSourceResolution, httpgrpc.Errorf(http.StatusBadRequest, errCannotParse, queryv1.MaxSourceResolutionParam)
   242  		}
   243  	}
   244  
   245  	if maxSourceResolution < 0 {
   246  		return 0, httpgrpc.Errorf(http.StatusBadRequest, "negative max_source_resolution is not accepted. Try a positive integer")
   247  	}
   248  
   249  	return maxSourceResolution, nil
   250  }
   251  
   252  func parsePartialResponseParam(s string, defaultEnablePartialResponse bool) (bool, error) {
   253  	if s != "" {
   254  		var err error
   255  		defaultEnablePartialResponse, err = strconv.ParseBool(s)
   256  		if err != nil {
   257  			return defaultEnablePartialResponse, httpgrpc.Errorf(http.StatusBadRequest, errCannotParse, queryv1.PartialResponseParam)
   258  		}
   259  	}
   260  
   261  	return defaultEnablePartialResponse, nil
   262  }
   263  
   264  func parseMatchersParam(ss url.Values, matcherParam string) ([][]*labels.Matcher, error) {
   265  	matchers := make([][]*labels.Matcher, 0, len(ss[matcherParam]))
   266  	for _, s := range ss[matcherParam] {
   267  		ms, err := parser.ParseMetricSelector(s)
   268  		if err != nil {
   269  			return nil, httpgrpc.Errorf(http.StatusBadRequest, errCannotParse, matcherParam)
   270  		}
   271  		matchers = append(matchers, ms)
   272  	}
   273  	return matchers, nil
   274  }
   275  
   276  func parseLookbackDelta(ss url.Values, key string) (int64, error) {
   277  	data, ok := ss[key]
   278  	if !ok || len(data) == 0 {
   279  		return 0, nil
   280  	}
   281  
   282  	return parseDurationMillis(data[0])
   283  }
   284  
   285  func parseShardInfo(ss url.Values, key string) (*storepb.ShardInfo, error) {
   286  	data, ok := ss[key]
   287  	if !ok || len(data) == 0 {
   288  		return nil, nil
   289  	}
   290  
   291  	var info storepb.ShardInfo
   292  	if err := json.Unmarshal([]byte(data[0]), &info); err != nil {
   293  		return nil, err
   294  	}
   295  
   296  	return &info, nil
   297  }
   298  
   299  func encodeTime(t int64) string {
   300  	f := float64(t) / 1.0e3
   301  	return strconv.FormatFloat(f, 'f', -1, 64)
   302  }
   303  
   304  func encodeDurationMillis(d int64) string {
   305  	return strconv.FormatFloat(float64(d)/float64(time.Second/time.Millisecond), 'f', -1, 64)
   306  }
   307  
   308  // matchersToStringSlice converts storeMatchers to string slice.
   309  func matchersToStringSlice(storeMatchers [][]*labels.Matcher) []string {
   310  	res := make([]string, 0, len(storeMatchers))
   311  	for _, storeMatcher := range storeMatchers {
   312  		res = append(res, storepb.PromMatchersToString(storeMatcher...))
   313  	}
   314  	return res
   315  }
   316  
   317  func encodeShardInfo(info *storepb.ShardInfo) (string, error) {
   318  	if info == nil {
   319  		return "", nil
   320  	}
   321  
   322  	data, err := json.Marshal(info)
   323  	if err != nil {
   324  		return "", err
   325  	}
   326  
   327  	return string(data), nil
   328  }