github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/querier/queryrange/queryrangebase/query_range.go (about)

     1  package queryrangebase
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"math"
     9  	"net/http"
    10  	"net/url"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/gogo/status"
    17  	jsoniter "github.com/json-iterator/go"
    18  	"github.com/opentracing/opentracing-go"
    19  	otlog "github.com/opentracing/opentracing-go/log"
    20  	"github.com/prometheus/common/model"
    21  	"github.com/prometheus/prometheus/model/timestamp"
    22  	"github.com/weaveworks/common/httpgrpc"
    23  
    24  	"github.com/grafana/loki/pkg/logproto"
    25  	"github.com/grafana/loki/pkg/util"
    26  	"github.com/grafana/loki/pkg/util/spanlogger"
    27  )
    28  
    29  // StatusSuccess Prometheus success result.
    30  const StatusSuccess = "success"
    31  
    32  var (
    33  	matrix = model.ValMatrix.String()
    34  	json   = jsoniter.Config{
    35  		EscapeHTML:             false, // No HTML in our responses.
    36  		SortMapKeys:            true,
    37  		ValidateJsonRawMessage: true,
    38  	}.Froze()
    39  	errEndBeforeStart = httpgrpc.Errorf(http.StatusBadRequest, "end timestamp must not be before start time")
    40  	errNegativeStep   = httpgrpc.Errorf(http.StatusBadRequest, "zero or negative query resolution step widths are not accepted. Try a positive integer")
    41  	errStepTooSmall   = httpgrpc.Errorf(http.StatusBadRequest, "exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)")
    42  
    43  	// PrometheusCodec is a codec to encode and decode Prometheus query range requests and responses.
    44  	PrometheusCodec Codec = &prometheusCodec{}
    45  
    46  	// Name of the cache control header.
    47  	cacheControlHeader = "Cache-Control"
    48  )
    49  
    50  type prometheusCodec struct{}
    51  
    52  // WithStartEnd clones the current `PrometheusRequest` with a new `start` and `end` timestamp.
    53  func (q *PrometheusRequest) WithStartEnd(start int64, end int64) Request {
    54  	new := *q
    55  	new.Start = start
    56  	new.End = end
    57  	return &new
    58  }
    59  
    60  // WithQuery clones the current `PrometheusRequest` with a new query.
    61  func (q *PrometheusRequest) WithQuery(query string) Request {
    62  	new := *q
    63  	new.Query = query
    64  	return &new
    65  }
    66  
    67  // LogToSpan logs the current `PrometheusRequest` parameters to the specified span.
    68  func (q *PrometheusRequest) LogToSpan(sp opentracing.Span) {
    69  	sp.LogFields(
    70  		otlog.String("query", q.GetQuery()),
    71  		otlog.String("start", timestamp.Time(q.GetStart()).String()),
    72  		otlog.String("end", timestamp.Time(q.GetEnd()).String()),
    73  		otlog.Int64("step (ms)", q.GetStep()),
    74  	)
    75  }
    76  
    77  type byFirstTime []*PrometheusResponse
    78  
    79  func (a byFirstTime) Len() int           { return len(a) }
    80  func (a byFirstTime) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
    81  func (a byFirstTime) Less(i, j int) bool { return a[i].minTime() < a[j].minTime() }
    82  
    83  func (resp *PrometheusResponse) minTime() int64 {
    84  	result := resp.Data.Result
    85  	if len(result) == 0 {
    86  		return -1
    87  	}
    88  	if len(result[0].Samples) == 0 {
    89  		return -1
    90  	}
    91  	return result[0].Samples[0].TimestampMs
    92  }
    93  
    94  // NewEmptyPrometheusResponse returns an empty successful Prometheus query range response.
    95  func NewEmptyPrometheusResponse() *PrometheusResponse {
    96  	return &PrometheusResponse{
    97  		Status: StatusSuccess,
    98  		Data: PrometheusData{
    99  			ResultType: model.ValMatrix.String(),
   100  			Result:     []SampleStream{},
   101  		},
   102  	}
   103  }
   104  
   105  func (prometheusCodec) MergeResponse(responses ...Response) (Response, error) {
   106  	if len(responses) == 0 {
   107  		return NewEmptyPrometheusResponse(), nil
   108  	}
   109  
   110  	promResponses := make([]*PrometheusResponse, 0, len(responses))
   111  	// we need to pass on all the headers for results cache gen numbers.
   112  	var resultsCacheGenNumberHeaderValues []string
   113  
   114  	for _, res := range responses {
   115  		promResponses = append(promResponses, res.(*PrometheusResponse))
   116  		resultsCacheGenNumberHeaderValues = append(resultsCacheGenNumberHeaderValues, getHeaderValuesWithName(res, ResultsCacheGenNumberHeaderName)...)
   117  	}
   118  
   119  	// Merge the responses.
   120  	sort.Sort(byFirstTime(promResponses))
   121  
   122  	response := PrometheusResponse{
   123  		Status: StatusSuccess,
   124  		Data: PrometheusData{
   125  			ResultType: model.ValMatrix.String(),
   126  			Result:     matrixMerge(promResponses),
   127  		},
   128  	}
   129  
   130  	if len(resultsCacheGenNumberHeaderValues) != 0 {
   131  		response.Headers = []*PrometheusResponseHeader{{
   132  			Name:   ResultsCacheGenNumberHeaderName,
   133  			Values: resultsCacheGenNumberHeaderValues,
   134  		}}
   135  	}
   136  
   137  	return &response, nil
   138  }
   139  
   140  func (prometheusCodec) DecodeRequest(_ context.Context, r *http.Request, forwardHeaders []string) (Request, error) {
   141  	var result PrometheusRequest
   142  	var err error
   143  	result.Start, err = util.ParseTime(r.FormValue("start"))
   144  	if err != nil {
   145  		return nil, decorateWithParamName(err, "start")
   146  	}
   147  
   148  	result.End, err = util.ParseTime(r.FormValue("end"))
   149  	if err != nil {
   150  		return nil, decorateWithParamName(err, "end")
   151  	}
   152  
   153  	if result.End < result.Start {
   154  		return nil, errEndBeforeStart
   155  	}
   156  
   157  	result.Step, err = parseDurationMs(r.FormValue("step"))
   158  	if err != nil {
   159  		return nil, decorateWithParamName(err, "step")
   160  	}
   161  
   162  	if result.Step <= 0 {
   163  		return nil, errNegativeStep
   164  	}
   165  
   166  	// For safety, limit the number of returned points per timeseries.
   167  	// This is sufficient for 60s resolution for a week or 1h resolution for a year.
   168  	if (result.End-result.Start)/result.Step > 11000 {
   169  		return nil, errStepTooSmall
   170  	}
   171  
   172  	result.Query = r.FormValue("query")
   173  	result.Path = r.URL.Path
   174  
   175  	// Include the specified headers from http request in prometheusRequest.
   176  	for _, header := range forwardHeaders {
   177  		for h, hv := range r.Header {
   178  			if strings.EqualFold(h, header) {
   179  				result.Headers = append(result.Headers, &PrometheusRequestHeader{Name: h, Values: hv})
   180  				break
   181  			}
   182  		}
   183  	}
   184  
   185  	for _, value := range r.Header.Values(cacheControlHeader) {
   186  		if strings.Contains(value, noStoreValue) {
   187  			result.CachingOptions.Disabled = true
   188  			break
   189  		}
   190  	}
   191  
   192  	return &result, nil
   193  }
   194  
   195  func (prometheusCodec) EncodeRequest(ctx context.Context, r Request) (*http.Request, error) {
   196  	promReq, ok := r.(*PrometheusRequest)
   197  	if !ok {
   198  		return nil, httpgrpc.Errorf(http.StatusBadRequest, "invalid request format")
   199  	}
   200  	params := url.Values{
   201  		"start": []string{encodeTime(promReq.Start)},
   202  		"end":   []string{encodeTime(promReq.End)},
   203  		"step":  []string{encodeDurationMs(promReq.Step)},
   204  		"query": []string{promReq.Query},
   205  	}
   206  	u := &url.URL{
   207  		Path:     promReq.Path,
   208  		RawQuery: params.Encode(),
   209  	}
   210  	var h = http.Header{}
   211  
   212  	for _, hv := range promReq.Headers {
   213  		for _, v := range hv.Values {
   214  			h.Add(hv.Name, v)
   215  		}
   216  	}
   217  
   218  	req := &http.Request{
   219  		Method:     "GET",
   220  		RequestURI: u.String(), // This is what the httpgrpc code looks at.
   221  		URL:        u,
   222  		Body:       http.NoBody,
   223  		Header:     h,
   224  	}
   225  
   226  	return req.WithContext(ctx), nil
   227  }
   228  
   229  func (prometheusCodec) DecodeResponse(ctx context.Context, r *http.Response, _ Request) (Response, error) {
   230  	if r.StatusCode/100 != 2 {
   231  		body, _ := ioutil.ReadAll(r.Body)
   232  		return nil, httpgrpc.Errorf(r.StatusCode, string(body))
   233  	}
   234  	log, ctx := spanlogger.New(ctx, "ParseQueryRangeResponse") //nolint:ineffassign,staticcheck
   235  	defer log.Finish()
   236  
   237  	buf, err := bodyBuffer(r)
   238  	if err != nil {
   239  		log.Error(err)
   240  		return nil, err
   241  	}
   242  	log.LogFields(otlog.Int("bytes", len(buf)))
   243  
   244  	var resp PrometheusResponse
   245  	if err := json.Unmarshal(buf, &resp); err != nil {
   246  		return nil, httpgrpc.Errorf(http.StatusInternalServerError, "error decoding response: %v", err)
   247  	}
   248  
   249  	for h, hv := range r.Header {
   250  		resp.Headers = append(resp.Headers, &PrometheusResponseHeader{Name: h, Values: hv})
   251  	}
   252  	return &resp, nil
   253  }
   254  
   255  // Buffer can be used to read a response body.
   256  // This allows to avoid reading the body multiple times from the `http.Response.Body`.
   257  type Buffer interface {
   258  	Bytes() []byte
   259  }
   260  
   261  func bodyBuffer(res *http.Response) ([]byte, error) {
   262  	// Attempt to cast the response body to a Buffer and use it if possible.
   263  	// This is because the frontend may have already read the body and buffered it.
   264  	if buffer, ok := res.Body.(Buffer); ok {
   265  		return buffer.Bytes(), nil
   266  	}
   267  	// Preallocate the buffer with the exact size so we don't waste allocations
   268  	// while progressively growing an initial small buffer. The buffer capacity
   269  	// is increased by MinRead to avoid extra allocations due to how ReadFrom()
   270  	// internally works.
   271  	buf := bytes.NewBuffer(make([]byte, 0, res.ContentLength+bytes.MinRead))
   272  	if _, err := buf.ReadFrom(res.Body); err != nil {
   273  		return nil, httpgrpc.Errorf(http.StatusInternalServerError, "error decoding response: %v", err)
   274  	}
   275  	return buf.Bytes(), nil
   276  }
   277  
   278  func (prometheusCodec) EncodeResponse(ctx context.Context, res Response) (*http.Response, error) {
   279  	sp, _ := opentracing.StartSpanFromContext(ctx, "APIResponse.ToHTTPResponse")
   280  	defer sp.Finish()
   281  
   282  	a, ok := res.(*PrometheusResponse)
   283  	if !ok {
   284  		return nil, httpgrpc.Errorf(http.StatusInternalServerError, "invalid response format")
   285  	}
   286  
   287  	sp.LogFields(otlog.Int("series", len(a.Data.Result)))
   288  
   289  	b, err := json.Marshal(a)
   290  	if err != nil {
   291  		return nil, httpgrpc.Errorf(http.StatusInternalServerError, "error encoding response: %v", err)
   292  	}
   293  
   294  	sp.LogFields(otlog.Int("bytes", len(b)))
   295  
   296  	resp := http.Response{
   297  		Header: http.Header{
   298  			"Content-Type": []string{"application/json"},
   299  		},
   300  		Body:          ioutil.NopCloser(bytes.NewBuffer(b)),
   301  		StatusCode:    http.StatusOK,
   302  		ContentLength: int64(len(b)),
   303  	}
   304  	return &resp, nil
   305  }
   306  
   307  // UnmarshalJSON implements json.Unmarshaler.
   308  func (s *SampleStream) UnmarshalJSON(data []byte) error {
   309  	var stream struct {
   310  		Metric model.Metric            `json:"metric"`
   311  		Values []logproto.LegacySample `json:"values"`
   312  	}
   313  	if err := json.Unmarshal(data, &stream); err != nil {
   314  		return err
   315  	}
   316  	s.Labels = logproto.FromMetricsToLabelAdapters(stream.Metric)
   317  	s.Samples = stream.Values
   318  	return nil
   319  }
   320  
   321  // MarshalJSON implements json.Marshaler.
   322  func (s *SampleStream) MarshalJSON() ([]byte, error) {
   323  	stream := struct {
   324  		Metric model.Metric            `json:"metric"`
   325  		Values []logproto.LegacySample `json:"values"`
   326  	}{
   327  		Metric: logproto.FromLabelAdaptersToMetric(s.Labels),
   328  		Values: s.Samples,
   329  	}
   330  	return json.Marshal(stream)
   331  }
   332  
   333  func matrixMerge(resps []*PrometheusResponse) []SampleStream {
   334  	output := map[string]*SampleStream{}
   335  	for _, resp := range resps {
   336  		for _, stream := range resp.Data.Result {
   337  			metric := logproto.FromLabelAdaptersToLabels(stream.Labels).String()
   338  			existing, ok := output[metric]
   339  			if !ok {
   340  				existing = &SampleStream{
   341  					Labels: stream.Labels,
   342  				}
   343  			}
   344  			// We need to make sure we don't repeat samples. This causes some visualisations to be broken in Grafana.
   345  			// The prometheus API is inclusive of start and end timestamps.
   346  			if len(existing.Samples) > 0 && len(stream.Samples) > 0 {
   347  				existingEndTs := existing.Samples[len(existing.Samples)-1].TimestampMs
   348  				if existingEndTs == stream.Samples[0].TimestampMs {
   349  					// Typically this the cases where only 1 sample point overlap,
   350  					// so optimize with simple code.
   351  					stream.Samples = stream.Samples[1:]
   352  				} else if existingEndTs > stream.Samples[0].TimestampMs {
   353  					// Overlap might be big, use heavier algorithm to remove overlap.
   354  					stream.Samples = sliceSamples(stream.Samples, existingEndTs)
   355  				} // else there is no overlap, yay!
   356  			}
   357  			existing.Samples = append(existing.Samples, stream.Samples...)
   358  			output[metric] = existing
   359  		}
   360  	}
   361  
   362  	keys := make([]string, 0, len(output))
   363  	for key := range output {
   364  		keys = append(keys, key)
   365  	}
   366  	sort.Strings(keys)
   367  
   368  	result := make([]SampleStream, 0, len(output))
   369  	for _, key := range keys {
   370  		result = append(result, *output[key])
   371  	}
   372  
   373  	return result
   374  }
   375  
   376  // sliceSamples assumes given samples are sorted by timestamp in ascending order and
   377  // return a sub slice whose first element's is the smallest timestamp that is strictly
   378  // bigger than the given minTs. Empty slice is returned if minTs is bigger than all the
   379  // timestamps in samples.
   380  func sliceSamples(samples []logproto.LegacySample, minTs int64) []logproto.LegacySample {
   381  	if len(samples) <= 0 || minTs < samples[0].TimestampMs {
   382  		return samples
   383  	}
   384  
   385  	if len(samples) > 0 && minTs > samples[len(samples)-1].TimestampMs {
   386  		return samples[len(samples):]
   387  	}
   388  
   389  	searchResult := sort.Search(len(samples), func(i int) bool {
   390  		return samples[i].TimestampMs > minTs
   391  	})
   392  
   393  	return samples[searchResult:]
   394  }
   395  
   396  func parseDurationMs(s string) (int64, error) {
   397  	if d, err := strconv.ParseFloat(s, 64); err == nil {
   398  		ts := d * float64(time.Second/time.Millisecond)
   399  		if ts > float64(math.MaxInt64) || ts < float64(math.MinInt64) {
   400  			return 0, httpgrpc.Errorf(http.StatusBadRequest, "cannot parse %q to a valid duration. It overflows int64", s)
   401  		}
   402  		return int64(ts), nil
   403  	}
   404  	if d, err := model.ParseDuration(s); err == nil {
   405  		return int64(d) / int64(time.Millisecond/time.Nanosecond), nil
   406  	}
   407  	return 0, httpgrpc.Errorf(http.StatusBadRequest, "cannot parse %q to a valid duration", s)
   408  }
   409  
   410  func encodeTime(t int64) string {
   411  	f := float64(t) / 1.0e3
   412  	return strconv.FormatFloat(f, 'f', -1, 64)
   413  }
   414  
   415  func encodeDurationMs(d int64) string {
   416  	return strconv.FormatFloat(float64(d)/float64(time.Second/time.Millisecond), 'f', -1, 64)
   417  }
   418  
   419  func decorateWithParamName(err error, field string) error {
   420  	errTmpl := "invalid parameter %q; %v"
   421  	if status, ok := status.FromError(err); ok {
   422  		return httpgrpc.Errorf(int(status.Code()), errTmpl, field, status.Message())
   423  	}
   424  	return fmt.Errorf(errTmpl, field, err)
   425  }