github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/common.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package prometheus 22 23 import ( 24 goerrors "errors" 25 "fmt" 26 "io" 27 "io/ioutil" 28 "net/http" 29 "time" 30 31 "github.com/golang/snappy" 32 33 "github.com/m3db/m3/src/query/errors" 34 "github.com/m3db/m3/src/query/models" 35 xpromql "github.com/m3db/m3/src/query/parser/promql" 36 "github.com/m3db/m3/src/query/storage" 37 "github.com/m3db/m3/src/query/storage/m3/consolidators" 38 "github.com/m3db/m3/src/query/ts" 39 "github.com/m3db/m3/src/query/util" 40 "github.com/m3db/m3/src/query/util/json" 41 xerrors "github.com/m3db/m3/src/x/errors" 42 xtime "github.com/m3db/m3/src/x/time" 43 ) 44 45 const ( 46 queryParam = "query" 47 endParam = "end" 48 startParam = "start" 49 nowTimeValue = "now" 50 timeParam = "time" 51 formatErrStr = "error parsing param: %s, error: %v" 52 53 filterNameTagsParam = "tag" 54 errFormatStr = "error parsing param: %s, error: %v" 55 tolerance = 0.0000001 56 ) 57 58 // ParsePromCompressedRequestResult is the result of a 59 // ParsePromCompressedRequest call. 60 type ParsePromCompressedRequestResult struct { 61 CompressedBody []byte 62 UncompressedBody []byte 63 } 64 65 // ParsePromCompressedRequest parses a snappy compressed request from Prometheus. 66 func ParsePromCompressedRequest( 67 r *http.Request, 68 ) (ParsePromCompressedRequestResult, error) { 69 body := r.Body 70 if r.Body == nil { 71 err := fmt.Errorf("empty request body") 72 return ParsePromCompressedRequestResult{}, 73 xerrors.NewInvalidParamsError(err) 74 } 75 76 defer body.Close() 77 78 compressed, err := ioutil.ReadAll(body) 79 if err != nil { 80 return ParsePromCompressedRequestResult{}, err 81 } 82 83 reqBuf, err := snappy.Decode(nil, compressed) 84 if err != nil { 85 return ParsePromCompressedRequestResult{}, 86 xerrors.NewInvalidParamsError(err) 87 } 88 89 return ParsePromCompressedRequestResult{ 90 CompressedBody: compressed, 91 UncompressedBody: reqBuf, 92 }, nil 93 } 94 95 // TagCompletionQueries are tag completion queries. 96 type TagCompletionQueries struct { 97 // Queries are the tag completion queries. 98 Queries []*storage.CompleteTagsQuery 99 // NameOnly indicates name only 100 NameOnly bool 101 } 102 103 // ParseTagCompletionParamsToQueries parses all params from the GET request. 104 // Returns queries, a boolean indicating if the query completes names only, and 105 // any errors. 106 func ParseTagCompletionParamsToQueries( 107 r *http.Request, 108 ) (TagCompletionQueries, error) { 109 tagCompletionQueries := TagCompletionQueries{} 110 start, err := util.ParseTimeStringWithDefault(r.FormValue("start"), 111 time.Unix(0, 0)) 112 if err != nil { 113 return tagCompletionQueries, xerrors.NewInvalidParamsError(err) 114 } 115 116 end, err := util.ParseTimeStringWithDefault(r.FormValue("end"), 117 time.Now()) 118 if err != nil { 119 return tagCompletionQueries, xerrors.NewInvalidParamsError(err) 120 } 121 122 // If there is a result type field present, parse it and set 123 // complete name only parameter appropriately. Otherwise, default 124 // to returning both completed tag names and values 125 nameOnly := false 126 if result := r.FormValue("result"); result != "" { 127 switch result { 128 case "default": 129 // no-op 130 case "tagNamesOnly": 131 nameOnly = true 132 default: 133 return tagCompletionQueries, xerrors.NewInvalidParamsError( 134 errors.ErrInvalidResultParamError) 135 } 136 } 137 138 tagCompletionQueries.NameOnly = nameOnly 139 queries, err := parseTagCompletionQueries(r) 140 if err != nil { 141 err = fmt.Errorf(errFormatStr, queryParam, err) 142 return tagCompletionQueries, xerrors.NewInvalidParamsError(err) 143 } 144 145 tagQueries := make([]*storage.CompleteTagsQuery, 0, len(queries)) 146 for _, query := range queries { 147 tagQuery := &storage.CompleteTagsQuery{ 148 Start: xtime.ToUnixNano(start), 149 End: xtime.ToUnixNano(end), 150 CompleteNameOnly: nameOnly, 151 } 152 153 matchers, err := models.MatchersFromString(query) 154 if err != nil { 155 return tagCompletionQueries, xerrors.NewInvalidParamsError(err) 156 } 157 158 tagQuery.TagMatchers = matchers 159 filterNameTags := r.Form[filterNameTagsParam] 160 tagQuery.FilterNameTags = make([][]byte, len(filterNameTags)) 161 for i, f := range filterNameTags { 162 tagQuery.FilterNameTags[i] = []byte(f) 163 } 164 165 tagQueries = append(tagQueries, tagQuery) 166 } 167 168 tagCompletionQueries.Queries = tagQueries 169 return tagCompletionQueries, nil 170 } 171 172 func parseTagCompletionQueries(r *http.Request) ([]string, error) { 173 queries, ok := r.URL.Query()[queryParam] 174 if !ok || len(queries) == 0 || queries[0] == "" { 175 return nil, xerrors.NewInvalidParamsError(errors.ErrNoQueryFound) 176 } 177 178 return queries, nil 179 } 180 181 // ParseStartAndEnd parses start and end params from the request. 182 func ParseStartAndEnd( 183 r *http.Request, 184 parseOpts xpromql.ParseOptions, 185 ) (time.Time, time.Time, error) { 186 if err := r.ParseForm(); err != nil { 187 return time.Time{}, time.Time{}, xerrors.NewInvalidParamsError(err) 188 } 189 190 defaultTime := time.Unix(0, 0) 191 start, err := util.ParseTimeStringWithDefault(r.FormValue("start"), defaultTime) 192 if err != nil { 193 return time.Time{}, time.Time{}, xerrors.NewInvalidParamsError(err) 194 } 195 196 if parseOpts.RequireStartEndTime() && start.Equal(defaultTime) { 197 return time.Time{}, time.Time{}, xerrors.NewInvalidParamsError( 198 goerrors.New("invalid start time. start time must be set")) 199 } 200 201 end, err := util.ParseTimeStringWithDefault(r.FormValue("end"), 202 parseOpts.NowFn()()) 203 if err != nil { 204 return time.Time{}, time.Time{}, xerrors.NewInvalidParamsError(err) 205 } 206 207 if start.After(end) { 208 err := fmt.Errorf("start %v must be after end %v", start, end) 209 return time.Time{}, time.Time{}, xerrors.NewInvalidParamsError(err) 210 } 211 212 return start, end, nil 213 } 214 215 // ParseSeriesMatchQuery parses all params from the GET request. 216 func ParseSeriesMatchQuery( 217 r *http.Request, 218 parseOpts xpromql.ParseOptions, 219 tagOptions models.TagOptions, 220 ) ([]*storage.FetchQuery, error) { 221 if err := r.ParseForm(); err != nil { 222 return nil, xerrors.NewInvalidParamsError(err) 223 } 224 225 matcherValues := r.Form["match[]"] 226 if len(matcherValues) == 0 { 227 return nil, xerrors.NewInvalidParamsError(errors.ErrInvalidMatchers) 228 } 229 230 start, end, err := ParseStartAndEnd(r, parseOpts) 231 if err != nil { 232 return nil, err 233 } 234 235 matchers, ok, err := ParseMatch(r, parseOpts, tagOptions) 236 if err != nil { 237 return nil, err 238 } 239 if !ok { 240 return nil, xerrors.NewInvalidParamsError( 241 fmt.Errorf("need more than one matcher: expected>=1, actual=%d", len(matchers))) 242 } 243 244 queries := make([]*storage.FetchQuery, 0, len(matcherValues)) 245 // nolint:gocritic 246 for _, m := range matchers { 247 queries = append(queries, &storage.FetchQuery{ 248 Raw: fmt.Sprintf("match[]=%s", m.Match), 249 TagMatchers: m.Matchers, 250 Start: start, 251 End: end, 252 }) 253 } 254 255 return queries, nil 256 } 257 258 // ParsedMatch is a parsed matched. 259 type ParsedMatch struct { 260 Match string 261 Matchers models.Matchers 262 } 263 264 // ParseMatch parses all match params from the GET request. 265 func ParseMatch( 266 r *http.Request, 267 parseOpts xpromql.ParseOptions, 268 tagOptions models.TagOptions, 269 ) ([]ParsedMatch, bool, error) { 270 if err := r.ParseForm(); err != nil { 271 return nil, false, xerrors.NewInvalidParamsError(err) 272 } 273 274 matcherValues := r.Form["match[]"] 275 if len(matcherValues) == 0 { 276 return nil, false, nil 277 } 278 279 matchers := make([]ParsedMatch, 0, len(matcherValues)) 280 for _, str := range matcherValues { 281 m, err := parseMatch(parseOpts, tagOptions, str) 282 if err != nil { 283 return nil, false, err 284 } 285 matchers = append(matchers, ParsedMatch{ 286 Match: str, 287 Matchers: m, 288 }) 289 } 290 291 return matchers, true, nil 292 } 293 294 func parseMatch( 295 parseOpts xpromql.ParseOptions, 296 tagOptions models.TagOptions, 297 matcher string, 298 ) (models.Matchers, error) { 299 fn := parseOpts.MetricSelectorFn() 300 301 promMatchers, err := fn(matcher) 302 if err != nil { 303 return nil, xerrors.NewInvalidParamsError(err) 304 } 305 306 matchers, err := xpromql.LabelMatchersToModelMatcher(promMatchers, tagOptions) 307 if err != nil { 308 return nil, xerrors.NewInvalidParamsError(err) 309 } 310 311 return matchers, nil 312 } 313 314 func renderNameOnlyTagCompletionResultsJSON( 315 w io.Writer, 316 results []consolidators.CompletedTag, 317 opts RenderSeriesMetadataOptions, 318 ) (RenderSeriesMetadataResult, error) { 319 var ( 320 total = len(results) 321 rendered = 0 322 limited bool 323 ) 324 325 jw := json.NewWriter(w) 326 jw.BeginArray() 327 328 for _, tag := range results { 329 if opts.ReturnedSeriesMetadataLimit > 0 && rendered >= opts.ReturnedSeriesMetadataLimit { 330 limited = true 331 break 332 } 333 rendered++ 334 jw.WriteBytesString(tag.Name) 335 } 336 337 jw.EndArray() 338 339 return RenderSeriesMetadataResult{ 340 Results: rendered, 341 TotalResults: total, 342 LimitedMaxReturnedData: limited, 343 }, jw.Close() 344 } 345 346 func renderDefaultTagCompletionResultsJSON( 347 w io.Writer, 348 results []consolidators.CompletedTag, 349 opts RenderSeriesMetadataOptions, 350 ) (RenderSeriesMetadataResult, error) { 351 var ( 352 total = 0 353 rendered = 0 354 limited bool 355 ) 356 357 jw := json.NewWriter(w) 358 jw.BeginObject() 359 360 jw.BeginObjectField("hits") 361 jw.WriteInt(len(results)) 362 363 jw.BeginObjectField("tags") 364 jw.BeginArray() 365 366 for _, tag := range results { 367 total += len(tag.Values) 368 if opts.ReturnedSeriesMetadataLimit > 0 && rendered >= opts.ReturnedSeriesMetadataLimit { 369 limited = true 370 continue 371 } 372 373 jw.BeginObject() 374 375 jw.BeginObjectField("key") 376 jw.WriteBytesString(tag.Name) 377 378 jw.BeginObjectField("values") 379 jw.BeginArray() 380 for _, value := range tag.Values { 381 if opts.ReturnedSeriesMetadataLimit > 0 && rendered >= opts.ReturnedSeriesMetadataLimit { 382 limited = true 383 break 384 } 385 rendered++ 386 387 jw.WriteBytesString(value) 388 } 389 jw.EndArray() 390 391 jw.EndObject() 392 } 393 jw.EndArray() 394 395 jw.EndObject() 396 397 return RenderSeriesMetadataResult{ 398 Results: rendered, 399 TotalResults: total, 400 LimitedMaxReturnedData: limited, 401 }, jw.Close() 402 } 403 404 // RenderSeriesMetadataOptions is a set of options for rendering 405 // series metadata. 406 type RenderSeriesMetadataOptions struct { 407 ReturnedSeriesMetadataLimit int 408 } 409 410 // RenderSeriesMetadataResult returns results about a series metadata rendering. 411 type RenderSeriesMetadataResult struct { 412 // Results is how many results were rendered. 413 Results int 414 // TotalResults is how many results in total there were regardless 415 // of rendering. 416 TotalResults int 417 // LimitedMaxReturnedData indicates if results rendering 418 // was truncated by a limit. 419 LimitedMaxReturnedData bool 420 } 421 422 // RenderListTagResultsJSON renders list tag results to json format. 423 func RenderListTagResultsJSON( 424 w io.Writer, 425 result *consolidators.CompleteTagsResult, 426 opts RenderSeriesMetadataOptions, 427 ) (RenderSeriesMetadataResult, error) { 428 if !result.CompleteNameOnly { 429 return RenderSeriesMetadataResult{}, errors.ErrWithNames 430 } 431 432 var ( 433 total = len(result.CompletedTags) 434 rendered = 0 435 limited bool 436 ) 437 438 jw := json.NewWriter(w) 439 jw.BeginObject() 440 441 jw.BeginObjectField("status") 442 jw.WriteString("success") 443 444 jw.BeginObjectField("data") 445 jw.BeginArray() 446 447 for _, t := range result.CompletedTags { 448 if opts.ReturnedSeriesMetadataLimit > 0 && rendered >= opts.ReturnedSeriesMetadataLimit { 449 limited = true 450 break 451 } 452 rendered++ 453 jw.WriteBytesString(t.Name) 454 } 455 456 jw.EndArray() 457 jw.EndObject() 458 459 return RenderSeriesMetadataResult{ 460 Results: rendered, 461 TotalResults: total, 462 LimitedMaxReturnedData: limited, 463 }, jw.Close() 464 } 465 466 // RenderTagCompletionResultsJSON renders tag completion results to json format. 467 func RenderTagCompletionResultsJSON( 468 w io.Writer, 469 result consolidators.CompleteTagsResult, 470 opts RenderSeriesMetadataOptions, 471 ) (RenderSeriesMetadataResult, error) { 472 results := result.CompletedTags 473 if result.CompleteNameOnly { 474 return renderNameOnlyTagCompletionResultsJSON(w, results, opts) 475 } 476 477 return renderDefaultTagCompletionResultsJSON(w, results, opts) 478 } 479 480 // RenderTagValuesResultsJSON renders tag values results to json format. 481 func RenderTagValuesResultsJSON( 482 w io.Writer, 483 result *consolidators.CompleteTagsResult, 484 opts RenderSeriesMetadataOptions, 485 ) (RenderSeriesMetadataResult, error) { 486 if result.CompleteNameOnly { 487 return RenderSeriesMetadataResult{}, errors.ErrNamesOnly 488 } 489 490 tagCount := len(result.CompletedTags) 491 if tagCount > 1 { 492 return RenderSeriesMetadataResult{}, errors.ErrMultipleResults 493 } 494 495 var ( 496 total = 0 497 rendered = 0 498 limited bool 499 ) 500 501 jw := json.NewWriter(w) 502 jw.BeginObject() 503 504 jw.BeginObjectField("status") 505 jw.WriteString("success") 506 507 jw.BeginObjectField("data") 508 jw.BeginArray() 509 510 if tagCount > 0 { 511 // We have our single expected result. 512 values := result.CompletedTags[0].Values 513 total += len(values) 514 for _, value := range values { 515 if opts.ReturnedSeriesMetadataLimit > 0 && rendered >= opts.ReturnedSeriesMetadataLimit { 516 limited = true 517 break 518 } 519 rendered++ 520 jw.WriteBytesString(value) 521 } 522 } 523 524 jw.EndArray() 525 526 jw.EndObject() 527 528 return RenderSeriesMetadataResult{ 529 Results: rendered, 530 TotalResults: total, 531 LimitedMaxReturnedData: limited, 532 }, jw.Close() 533 } 534 535 // RenderSeriesMatchResultsJSON renders series match results to json format. 536 func RenderSeriesMatchResultsJSON( 537 w io.Writer, 538 results []models.Metrics, 539 opts RenderSeriesMetadataOptions, 540 ) (RenderSeriesMetadataResult, error) { 541 var ( 542 total = 0 543 rendered = 0 544 limited bool 545 ) 546 547 jw := json.NewWriter(w) 548 jw.BeginObject() 549 550 jw.BeginObjectField("status") 551 jw.WriteString("success") 552 553 jw.BeginObjectField("data") 554 jw.BeginArray() 555 556 for _, result := range results { 557 for _, tags := range result { 558 total += len(tags.Tags.Tags) 559 if opts.ReturnedSeriesMetadataLimit > 0 && rendered >= opts.ReturnedSeriesMetadataLimit { 560 limited = true 561 continue 562 } 563 564 jw.BeginObject() 565 for _, tag := range tags.Tags.Tags { 566 if opts.ReturnedSeriesMetadataLimit > 0 && rendered >= opts.ReturnedSeriesMetadataLimit { 567 limited = true 568 break 569 } 570 rendered++ 571 572 jw.BeginObjectBytesField(tag.Name) 573 jw.WriteBytesString(tag.Value) 574 } 575 576 jw.EndObject() 577 } 578 } 579 580 jw.EndArray() 581 jw.EndObject() 582 583 return RenderSeriesMetadataResult{ 584 Results: rendered, 585 TotalResults: total, 586 LimitedMaxReturnedData: limited, 587 }, jw.Close() 588 } 589 590 // FilterSeriesByOptions removes series tags based on options. 591 func FilterSeriesByOptions( 592 series []*ts.Series, 593 opts *storage.FetchOptions, 594 ) []*ts.Series { 595 if opts == nil { 596 return series 597 } 598 599 keys := opts.RestrictQueryOptions.GetRestrictByTag().GetFilterByNames() 600 if len(keys) > 0 { 601 for i, s := range series { 602 series[i].Tags = s.Tags.TagsWithoutKeys(keys) 603 } 604 } 605 606 return series 607 } 608 609 // ParseTime parses a time out of a request key, with a default value. 610 func ParseTime(r *http.Request, key string, now time.Time) (time.Time, error) { 611 if t := r.FormValue(key); t != "" { 612 if t == nowTimeValue { 613 return now, nil 614 } 615 return util.ParseTimeString(t) 616 } 617 return time.Time{}, errors.ErrNotFound 618 } 619 620 // TimeParams represents the time parameters within a request. 621 type TimeParams struct { 622 Now time.Time 623 Start time.Time 624 End time.Time 625 } 626 627 // ParseTimeParams parses the time params (now, start, end) from a request. 628 func ParseTimeParams(r *http.Request) (TimeParams, error) { 629 var ( 630 params TimeParams 631 err error 632 ) 633 634 params.Now = time.Now() 635 if v := r.FormValue(timeParam); v != "" { 636 var err error 637 params.Now, err = ParseTime(r, timeParam, params.Now) 638 if err != nil { 639 err = fmt.Errorf(formatErrStr, timeParam, err) 640 return params, xerrors.NewInvalidParamsError(err) 641 } 642 } 643 644 params.Start, err = ParseTime(r, startParam, params.Now) 645 if err != nil { 646 err = fmt.Errorf(formatErrStr, startParam, err) 647 return params, xerrors.NewInvalidParamsError(err) 648 } 649 650 params.End, err = ParseTime(r, endParam, params.Now) 651 if err != nil { 652 err = fmt.Errorf(formatErrStr, endParam, err) 653 return params, xerrors.NewInvalidParamsError(err) 654 } 655 if params.Start.After(params.End) { 656 err = fmt.Errorf("start (%s) must be before end (%s)", params.Start, params.End) 657 return params, xerrors.NewInvalidParamsError(err) 658 } 659 660 return params, nil 661 } 662 663 // SetDefaultStartEndParamsForInstant sets the start and end values for instant queries. Instant queries 664 // don't specify start and end, but these params are required to be set to be successfully processed 665 // by storage. 666 func SetDefaultStartEndParamsForInstant(r *http.Request) { 667 r.Form.Set(startParam, nowTimeValue) 668 r.Form.Set(endParam, nowTimeValue) 669 }