github.com/thanos-io/thanos@v0.32.5/pkg/queryfrontend/queryinstant_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  	"io"
    11  	"net/http"
    12  	"net/url"
    13  	"sort"
    14  	"strconv"
    15  	"strings"
    16  
    17  	"github.com/opentracing/opentracing-go"
    18  	otlog "github.com/opentracing/opentracing-go/log"
    19  	"github.com/prometheus/common/model"
    20  	"github.com/weaveworks/common/httpgrpc"
    21  
    22  	"github.com/prometheus/prometheus/promql/parser"
    23  	"github.com/thanos-io/thanos/internal/cortex/cortexpb"
    24  	"github.com/thanos-io/thanos/internal/cortex/querier/queryrange"
    25  	cortexutil "github.com/thanos-io/thanos/internal/cortex/util"
    26  	"github.com/thanos-io/thanos/internal/cortex/util/spanlogger"
    27  	queryv1 "github.com/thanos-io/thanos/pkg/api/query"
    28  )
    29  
    30  // queryInstantCodec is used to encode/decode Thanos instant query requests and responses.
    31  type queryInstantCodec struct {
    32  	partialResponse bool
    33  }
    34  
    35  // NewThanosQueryInstantCodec initializes a queryInstantCodec.
    36  func NewThanosQueryInstantCodec(partialResponse bool) *queryInstantCodec {
    37  	return &queryInstantCodec{
    38  		partialResponse: partialResponse,
    39  	}
    40  }
    41  
    42  // MergeResponse merges multiple responses into a single response. For instant query
    43  // only vector and matrix responses will be merged because other types of queries
    44  // are not shardable like number literal, string literal, scalar, etc.
    45  func (c queryInstantCodec) MergeResponse(req queryrange.Request, responses ...queryrange.Response) (queryrange.Response, error) {
    46  	if len(responses) == 0 {
    47  		return queryrange.NewEmptyPrometheusInstantQueryResponse(), nil
    48  	} else if len(responses) == 1 {
    49  		return responses[0], nil
    50  	}
    51  
    52  	promResponses := make([]*queryrange.PrometheusInstantQueryResponse, 0, len(responses))
    53  	for _, resp := range responses {
    54  		promResponses = append(promResponses, resp.(*queryrange.PrometheusInstantQueryResponse))
    55  	}
    56  
    57  	var explanation *queryrange.Explanation
    58  	for i := range promResponses {
    59  		if promResponses[i].Data.GetExplanation() != nil {
    60  			explanation = promResponses[i].Data.GetExplanation()
    61  			break
    62  		}
    63  	}
    64  
    65  	var res queryrange.Response
    66  	switch promResponses[0].Data.ResultType {
    67  	case model.ValMatrix.String():
    68  		res = &queryrange.PrometheusInstantQueryResponse{
    69  			Status: queryrange.StatusSuccess,
    70  			Data: queryrange.PrometheusInstantQueryData{
    71  				ResultType: model.ValMatrix.String(),
    72  				Result: queryrange.PrometheusInstantQueryResult{
    73  					Result: &queryrange.PrometheusInstantQueryResult_Matrix{
    74  						Matrix: matrixMerge(promResponses),
    75  					},
    76  				},
    77  				Stats:       queryrange.StatsMerge(responses),
    78  				Explanation: explanation,
    79  			},
    80  		}
    81  	default:
    82  		v, err := vectorMerge(req, promResponses)
    83  		if err != nil {
    84  			return nil, err
    85  		}
    86  		res = &queryrange.PrometheusInstantQueryResponse{
    87  			Status: queryrange.StatusSuccess,
    88  			Data: queryrange.PrometheusInstantQueryData{
    89  				ResultType: model.ValVector.String(),
    90  				Result: queryrange.PrometheusInstantQueryResult{
    91  					Result: &queryrange.PrometheusInstantQueryResult_Vector{
    92  						Vector: v,
    93  					},
    94  				},
    95  				Stats:       queryrange.StatsMerge(responses),
    96  				Explanation: explanation,
    97  			},
    98  		}
    99  	}
   100  
   101  	return res, nil
   102  }
   103  
   104  func (c queryInstantCodec) DecodeRequest(_ context.Context, r *http.Request, forwardHeaders []string) (queryrange.Request, error) {
   105  	var (
   106  		result ThanosQueryInstantRequest
   107  		err    error
   108  	)
   109  	if len(r.FormValue("time")) > 0 {
   110  		result.Time, err = cortexutil.ParseTime(r.FormValue("time"))
   111  		if err != nil {
   112  			return nil, err
   113  		}
   114  	}
   115  
   116  	result.Dedup, err = parseEnableDedupParam(r.FormValue(queryv1.DedupParam))
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	if r.FormValue(queryv1.MaxSourceResolutionParam) == "auto" {
   122  		result.AutoDownsampling = true
   123  	} else {
   124  		result.MaxSourceResolution, err = parseDownsamplingParamMillis(r.FormValue(queryv1.MaxSourceResolutionParam))
   125  		if err != nil {
   126  			return nil, err
   127  		}
   128  	}
   129  
   130  	result.PartialResponse, err = parsePartialResponseParam(r.FormValue(queryv1.PartialResponseParam), c.partialResponse)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	if len(r.Form[queryv1.ReplicaLabelsParam]) > 0 {
   136  		result.ReplicaLabels = r.Form[queryv1.ReplicaLabelsParam]
   137  	}
   138  
   139  	result.StoreMatchers, err = parseMatchersParam(r.Form, queryv1.StoreMatcherParam)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	result.ShardInfo, err = parseShardInfo(r.Form, queryv1.ShardInfoParam)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	result.LookbackDelta, err = parseLookbackDelta(r.Form, queryv1.LookbackDeltaParam)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	result.Query = r.FormValue("query")
   155  	result.Path = r.URL.Path
   156  	result.Explain = r.FormValue(queryv1.QueryExplainParam)
   157  	result.Engine = r.FormValue("engine")
   158  
   159  	for _, header := range forwardHeaders {
   160  		for h, hv := range r.Header {
   161  			if strings.EqualFold(h, header) {
   162  				result.Headers = append(result.Headers, &RequestHeader{Name: h, Values: hv})
   163  				break
   164  			}
   165  		}
   166  	}
   167  	return &result, nil
   168  }
   169  
   170  func (c queryInstantCodec) EncodeRequest(ctx context.Context, r queryrange.Request) (*http.Request, error) {
   171  	thanosReq, ok := r.(*ThanosQueryInstantRequest)
   172  	if !ok {
   173  		return nil, httpgrpc.Errorf(http.StatusBadRequest, "invalid request format")
   174  	}
   175  	params := url.Values{
   176  		"query":                      []string{thanosReq.Query},
   177  		queryv1.DedupParam:           []string{strconv.FormatBool(thanosReq.Dedup)},
   178  		queryv1.PartialResponseParam: []string{strconv.FormatBool(thanosReq.PartialResponse)},
   179  		queryv1.QueryExplainParam:    []string{thanosReq.Explain},
   180  		queryv1.EngineParam:          []string{thanosReq.Engine},
   181  		queryv1.ReplicaLabelsParam:   thanosReq.ReplicaLabels,
   182  	}
   183  
   184  	if thanosReq.Time > 0 {
   185  		params["time"] = []string{encodeTime(thanosReq.Time)}
   186  	}
   187  	if thanosReq.AutoDownsampling {
   188  		params[queryv1.MaxSourceResolutionParam] = []string{"auto"}
   189  	} else if thanosReq.MaxSourceResolution != 0 {
   190  		// Add this param only if it is set. Set to 0 will impact
   191  		// auto-downsampling in the querier.
   192  		params[queryv1.MaxSourceResolutionParam] = []string{encodeDurationMillis(thanosReq.MaxSourceResolution)}
   193  	}
   194  
   195  	if len(thanosReq.StoreMatchers) > 0 {
   196  		params[queryv1.StoreMatcherParam] = matchersToStringSlice(thanosReq.StoreMatchers)
   197  	}
   198  
   199  	if thanosReq.ShardInfo != nil {
   200  		data, err := encodeShardInfo(thanosReq.ShardInfo)
   201  		if err != nil {
   202  			return nil, err
   203  		}
   204  		params[queryv1.ShardInfoParam] = []string{data}
   205  	}
   206  
   207  	if thanosReq.LookbackDelta > 0 {
   208  		params[queryv1.LookbackDeltaParam] = []string{encodeDurationMillis(thanosReq.LookbackDelta)}
   209  	}
   210  
   211  	req, err := http.NewRequest(http.MethodPost, thanosReq.Path, bytes.NewBufferString(params.Encode()))
   212  	if err != nil {
   213  		return nil, httpgrpc.Errorf(http.StatusBadRequest, "error creating request: %s", err.Error())
   214  	}
   215  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   216  	for _, hv := range thanosReq.Headers {
   217  		for _, v := range hv.Values {
   218  			req.Header.Add(hv.Name, v)
   219  		}
   220  	}
   221  	return req.WithContext(ctx), nil
   222  }
   223  
   224  func (c queryInstantCodec) EncodeResponse(ctx context.Context, res queryrange.Response) (*http.Response, error) {
   225  	sp, _ := opentracing.StartSpanFromContext(ctx, "APIResponse.ToHTTPResponse")
   226  	defer sp.Finish()
   227  
   228  	a, ok := res.(*queryrange.PrometheusInstantQueryResponse)
   229  	if !ok {
   230  		return nil, httpgrpc.Errorf(http.StatusInternalServerError, "invalid response format")
   231  	}
   232  
   233  	b, err := json.Marshal(a)
   234  	if err != nil {
   235  		return nil, httpgrpc.Errorf(http.StatusInternalServerError, "error encoding response: %v", err)
   236  	}
   237  
   238  	sp.LogFields(otlog.Int("bytes", len(b)))
   239  
   240  	resp := http.Response{
   241  		Header: http.Header{
   242  			"Content-Type": []string{"application/json"},
   243  		},
   244  		Body:          io.NopCloser(bytes.NewBuffer(b)),
   245  		StatusCode:    http.StatusOK,
   246  		ContentLength: int64(len(b)),
   247  	}
   248  	return &resp, nil
   249  }
   250  
   251  func (c queryInstantCodec) DecodeResponse(ctx context.Context, r *http.Response, req queryrange.Request) (queryrange.Response, error) {
   252  	if r.StatusCode/100 != 2 {
   253  		body, _ := io.ReadAll(r.Body)
   254  		return nil, httpgrpc.Errorf(r.StatusCode, string(body))
   255  	}
   256  	log, ctx := spanlogger.New(ctx, "ParseQueryInstantResponse") //nolint:ineffassign,staticcheck
   257  	defer log.Finish()
   258  
   259  	buf, err := queryrange.BodyBuffer(r)
   260  	if err != nil {
   261  		log.Error(err) //nolint:errcheck
   262  		return nil, err
   263  	}
   264  	log.LogFields(otlog.Int("bytes", len(buf)))
   265  
   266  	var resp queryrange.PrometheusInstantQueryResponse
   267  	if err := json.Unmarshal(buf, &resp); err != nil {
   268  		return nil, httpgrpc.Errorf(http.StatusInternalServerError, "error decoding response: %v", err)
   269  	}
   270  
   271  	for h, hv := range r.Header {
   272  		resp.Headers = append(resp.Headers, &queryrange.PrometheusResponseHeader{Name: h, Values: hv})
   273  	}
   274  	return &resp, nil
   275  }
   276  
   277  func vectorMerge(req queryrange.Request, resps []*queryrange.PrometheusInstantQueryResponse) (*queryrange.Vector, error) {
   278  	output := map[string]*queryrange.Sample{}
   279  	metrics := []string{} // Used to preserve the order for topk and bottomk.
   280  	sortPlan, err := sortPlanForQuery(req.GetQuery())
   281  	if err != nil {
   282  		return nil, err
   283  	}
   284  	for _, resp := range resps {
   285  		if resp == nil {
   286  			continue
   287  		}
   288  		// Merge vector result samples only. Skip other types such as
   289  		// string, scalar as those are not sharable.
   290  		if resp.Data.Result.GetVector() == nil {
   291  			continue
   292  		}
   293  		for _, sample := range resp.Data.Result.GetVector().Samples {
   294  			s := sample
   295  			if s == nil {
   296  				continue
   297  			}
   298  			metric := cortexpb.FromLabelAdaptersToLabels(sample.Labels).String()
   299  			if existingSample, ok := output[metric]; !ok {
   300  				output[metric] = s
   301  				metrics = append(metrics, metric) // Preserve the order of metric.
   302  			} else if existingSample.Timestamp < s.Timestamp {
   303  				// Choose the latest sample if we see overlap.
   304  				output[metric] = s
   305  			}
   306  		}
   307  	}
   308  
   309  	result := &queryrange.Vector{
   310  		Samples: make([]*queryrange.Sample, 0, len(output)),
   311  	}
   312  
   313  	if len(output) == 0 {
   314  		return result, nil
   315  	}
   316  
   317  	if sortPlan == mergeOnly {
   318  		for _, k := range metrics {
   319  			result.Samples = append(result.Samples, output[k])
   320  		}
   321  		return result, nil
   322  	}
   323  
   324  	type pair struct {
   325  		metric string
   326  		s      *queryrange.Sample
   327  	}
   328  
   329  	samples := make([]*pair, 0, len(output))
   330  	for k, v := range output {
   331  		samples = append(samples, &pair{
   332  			metric: k,
   333  			s:      v,
   334  		})
   335  	}
   336  
   337  	sort.Slice(samples, func(i, j int) bool {
   338  		// Order is determined by vector
   339  		switch sortPlan {
   340  		case sortByValuesAsc:
   341  			return samples[i].s.SampleValue < samples[j].s.SampleValue
   342  		case sortByValuesDesc:
   343  			return samples[i].s.SampleValue > samples[j].s.SampleValue
   344  		}
   345  		return samples[i].metric < samples[j].metric
   346  	})
   347  
   348  	for _, p := range samples {
   349  		result.Samples = append(result.Samples, p.s)
   350  	}
   351  	return result, nil
   352  }
   353  
   354  type sortPlan int
   355  
   356  const (
   357  	mergeOnly        sortPlan = 0
   358  	sortByValuesAsc  sortPlan = 1
   359  	sortByValuesDesc sortPlan = 2
   360  	sortByLabels     sortPlan = 3
   361  )
   362  
   363  func sortPlanForQuery(q string) (sortPlan, error) {
   364  	expr, err := parser.ParseExpr(q)
   365  	if err != nil {
   366  		return 0, err
   367  	}
   368  	// Check if the root expression is topk or bottomk
   369  	if aggr, ok := expr.(*parser.AggregateExpr); ok {
   370  		if aggr.Op == parser.TOPK || aggr.Op == parser.BOTTOMK {
   371  			return mergeOnly, nil
   372  		}
   373  	}
   374  	checkForSort := func(expr parser.Expr) (sortAsc, sortDesc bool) {
   375  		if n, ok := expr.(*parser.Call); ok {
   376  			if n.Func != nil {
   377  				if n.Func.Name == "sort" {
   378  					sortAsc = true
   379  				}
   380  				if n.Func.Name == "sort_desc" {
   381  					sortDesc = true
   382  				}
   383  			}
   384  		}
   385  		return sortAsc, sortDesc
   386  	}
   387  	// Check the root expression for sort
   388  	if sortAsc, sortDesc := checkForSort(expr); sortAsc || sortDesc {
   389  		if sortAsc {
   390  			return sortByValuesAsc, nil
   391  		}
   392  		return sortByValuesDesc, nil
   393  	}
   394  
   395  	// If the root expression is a binary expression, check the LHS and RHS for sort
   396  	if bin, ok := expr.(*parser.BinaryExpr); ok {
   397  		if sortAsc, sortDesc := checkForSort(bin.LHS); sortAsc || sortDesc {
   398  			if sortAsc {
   399  				return sortByValuesAsc, nil
   400  			}
   401  			return sortByValuesDesc, nil
   402  		}
   403  		if sortAsc, sortDesc := checkForSort(bin.RHS); sortAsc || sortDesc {
   404  			if sortAsc {
   405  				return sortByValuesAsc, nil
   406  			}
   407  			return sortByValuesDesc, nil
   408  		}
   409  	}
   410  	return sortByLabels, nil
   411  }
   412  
   413  func matrixMerge(resps []*queryrange.PrometheusInstantQueryResponse) *queryrange.Matrix {
   414  	output := map[string]*queryrange.SampleStream{}
   415  	for _, resp := range resps {
   416  		if resp == nil {
   417  			continue
   418  		}
   419  		// Merge matrix result samples only. Skip other types such as
   420  		// string, scalar as those are not sharable.
   421  		if resp.Data.Result.GetMatrix() == nil {
   422  			continue
   423  		}
   424  		for _, stream := range resp.Data.Result.GetMatrix().SampleStreams {
   425  			metric := cortexpb.FromLabelAdaptersToLabels(stream.Labels).String()
   426  			existing, ok := output[metric]
   427  			if !ok {
   428  				existing = &queryrange.SampleStream{
   429  					Labels: stream.Labels,
   430  				}
   431  			}
   432  			// We need to make sure we don't repeat samples. This causes some visualizations to be broken in Grafana.
   433  			// The prometheus API is inclusive of start and end timestamps.
   434  			if len(existing.Samples) > 0 && len(stream.Samples) > 0 {
   435  				existingEndTs := existing.Samples[len(existing.Samples)-1].TimestampMs
   436  				if existingEndTs == stream.Samples[0].TimestampMs {
   437  					// Typically this the cases where only 1 sample point overlap,
   438  					// so optimize with simple code.
   439  					stream.Samples = stream.Samples[1:]
   440  				} else if existingEndTs > stream.Samples[0].TimestampMs {
   441  					// Overlap might be big, use heavier algorithm to remove overlap.
   442  					stream.Samples = queryrange.SliceSamples(stream.Samples, existingEndTs)
   443  				} // else there is no overlap, yay!
   444  			}
   445  			existing.Samples = append(existing.Samples, stream.Samples...)
   446  			output[metric] = existing
   447  		}
   448  	}
   449  
   450  	keys := make([]string, 0, len(output))
   451  	for key := range output {
   452  		keys = append(keys, key)
   453  	}
   454  	sort.Strings(keys)
   455  
   456  	result := &queryrange.Matrix{
   457  		SampleStreams: make([]*queryrange.SampleStream, 0, len(output)),
   458  	}
   459  	for _, key := range keys {
   460  		result.SampleStreams = append(result.SampleStreams, output[key])
   461  	}
   462  
   463  	return result
   464  }