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 }