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 }