github.com/thanos-io/thanos@v0.32.5/pkg/api/query/v1.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 // Copyright 2016 The Prometheus Authors 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 // This package is a modified copy from 18 // github.com/prometheus/prometheus/web/api/v1@2121b4628baa7d9d9406aa468712a6a332e77aff. 19 20 package v1 21 22 import ( 23 "context" 24 "encoding/json" 25 "math" 26 "net/http" 27 "sort" 28 "strconv" 29 "strings" 30 "time" 31 32 "github.com/go-kit/log" 33 "github.com/opentracing/opentracing-go" 34 "github.com/pkg/errors" 35 "github.com/prometheus/client_golang/prometheus" 36 "github.com/prometheus/client_golang/prometheus/promauto" 37 "github.com/prometheus/common/model" 38 "github.com/prometheus/common/route" 39 "github.com/prometheus/prometheus/model/labels" 40 "github.com/prometheus/prometheus/model/timestamp" 41 "github.com/prometheus/prometheus/promql" 42 "github.com/prometheus/prometheus/promql/parser" 43 "github.com/prometheus/prometheus/storage" 44 "github.com/prometheus/prometheus/util/stats" 45 v1 "github.com/prometheus/prometheus/web/api/v1" 46 promqlapi "github.com/thanos-io/promql-engine/api" 47 "github.com/thanos-io/promql-engine/engine" 48 49 "github.com/thanos-io/thanos/pkg/api" 50 "github.com/thanos-io/thanos/pkg/exemplars" 51 "github.com/thanos-io/thanos/pkg/exemplars/exemplarspb" 52 extpromhttp "github.com/thanos-io/thanos/pkg/extprom/http" 53 "github.com/thanos-io/thanos/pkg/gate" 54 "github.com/thanos-io/thanos/pkg/logging" 55 "github.com/thanos-io/thanos/pkg/metadata" 56 "github.com/thanos-io/thanos/pkg/metadata/metadatapb" 57 "github.com/thanos-io/thanos/pkg/query" 58 "github.com/thanos-io/thanos/pkg/rules" 59 "github.com/thanos-io/thanos/pkg/rules/rulespb" 60 "github.com/thanos-io/thanos/pkg/runutil" 61 "github.com/thanos-io/thanos/pkg/store" 62 "github.com/thanos-io/thanos/pkg/store/storepb" 63 "github.com/thanos-io/thanos/pkg/targets" 64 "github.com/thanos-io/thanos/pkg/targets/targetspb" 65 "github.com/thanos-io/thanos/pkg/tenancy" 66 "github.com/thanos-io/thanos/pkg/tracing" 67 ) 68 69 const ( 70 DedupParam = "dedup" 71 PartialResponseParam = "partial_response" 72 MaxSourceResolutionParam = "max_source_resolution" 73 ReplicaLabelsParam = "replicaLabels[]" 74 MatcherParam = "match[]" 75 StoreMatcherParam = "storeMatch[]" 76 Step = "step" 77 Stats = "stats" 78 ShardInfoParam = "shard_info" 79 LookbackDeltaParam = "lookback_delta" 80 EngineParam = "engine" 81 QueryExplainParam = "explain" 82 ) 83 84 type PromqlEngineType string 85 86 const ( 87 PromqlEnginePrometheus PromqlEngineType = "prometheus" 88 PromqlEngineThanos PromqlEngineType = "thanos" 89 ) 90 91 type QueryEngineFactory struct { 92 engineOpts promql.EngineOpts 93 remoteEngineEndpoints promqlapi.RemoteEndpoints 94 95 prometheusEngine v1.QueryEngine 96 thanosEngine v1.QueryEngine 97 } 98 99 func (f *QueryEngineFactory) GetPrometheusEngine() v1.QueryEngine { 100 if f.prometheusEngine != nil { 101 return f.prometheusEngine 102 } 103 104 f.prometheusEngine = promql.NewEngine(f.engineOpts) 105 return f.prometheusEngine 106 } 107 108 func (f *QueryEngineFactory) GetThanosEngine() v1.QueryEngine { 109 if f.thanosEngine != nil { 110 return f.thanosEngine 111 } 112 113 if f.remoteEngineEndpoints == nil { 114 f.thanosEngine = engine.New(engine.Opts{EngineOpts: f.engineOpts, Engine: f.GetPrometheusEngine()}) 115 } else { 116 f.thanosEngine = engine.NewDistributedEngine(engine.Opts{EngineOpts: f.engineOpts, Engine: f.GetPrometheusEngine()}, f.remoteEngineEndpoints) 117 } 118 119 return f.thanosEngine 120 } 121 122 func NewQueryEngineFactory( 123 engineOpts promql.EngineOpts, 124 remoteEngineEndpoints promqlapi.RemoteEndpoints, 125 ) *QueryEngineFactory { 126 return &QueryEngineFactory{ 127 engineOpts: engineOpts, 128 remoteEngineEndpoints: remoteEngineEndpoints, 129 } 130 } 131 132 // QueryAPI is an API used by Thanos Querier. 133 type QueryAPI struct { 134 baseAPI *api.BaseAPI 135 logger log.Logger 136 gate gate.Gate 137 queryableCreate query.QueryableCreator 138 // queryEngine returns appropriate promql.Engine for a query with a given step. 139 engineFactory *QueryEngineFactory 140 defaultEngine PromqlEngineType 141 lookbackDeltaCreate func(int64) time.Duration 142 ruleGroups rules.UnaryClient 143 targets targets.UnaryClient 144 metadatas metadata.UnaryClient 145 exemplars exemplars.UnaryClient 146 147 enableAutodownsampling bool 148 enableQueryPartialResponse bool 149 enableRulePartialResponse bool 150 enableTargetPartialResponse bool 151 enableMetricMetadataPartialResponse bool 152 enableExemplarPartialResponse bool 153 enableQueryPushdown bool 154 disableCORS bool 155 156 replicaLabels []string 157 endpointStatus func() []query.EndpointStatus 158 159 defaultRangeQueryStep time.Duration 160 defaultInstantQueryMaxSourceResolution time.Duration 161 defaultMetadataTimeRange time.Duration 162 163 queryRangeHist prometheus.Histogram 164 165 seriesStatsAggregatorFactory store.SeriesQueryPerformanceMetricsAggregatorFactory 166 167 tenantHeader string 168 defaultTenant string 169 tenantCertField string 170 } 171 172 // NewQueryAPI returns an initialized QueryAPI type. 173 func NewQueryAPI( 174 logger log.Logger, 175 endpointStatus func() []query.EndpointStatus, 176 engineFactory *QueryEngineFactory, 177 defaultEngine PromqlEngineType, 178 lookbackDeltaCreate func(int64) time.Duration, 179 c query.QueryableCreator, 180 ruleGroups rules.UnaryClient, 181 targets targets.UnaryClient, 182 metadatas metadata.UnaryClient, 183 exemplars exemplars.UnaryClient, 184 enableAutodownsampling bool, 185 enableQueryPartialResponse bool, 186 enableRulePartialResponse bool, 187 enableTargetPartialResponse bool, 188 enableMetricMetadataPartialResponse bool, 189 enableExemplarPartialResponse bool, 190 enableQueryPushdown bool, 191 replicaLabels []string, 192 flagsMap map[string]string, 193 defaultRangeQueryStep time.Duration, 194 defaultInstantQueryMaxSourceResolution time.Duration, 195 defaultMetadataTimeRange time.Duration, 196 disableCORS bool, 197 gate gate.Gate, 198 statsAggregatorFactory store.SeriesQueryPerformanceMetricsAggregatorFactory, 199 reg *prometheus.Registry, 200 tenantHeader string, 201 defaultTenant string, 202 tenantCertField string, 203 ) *QueryAPI { 204 if statsAggregatorFactory == nil { 205 statsAggregatorFactory = &store.NoopSeriesStatsAggregatorFactory{} 206 } 207 return &QueryAPI{ 208 baseAPI: api.NewBaseAPI(logger, disableCORS, flagsMap), 209 logger: logger, 210 engineFactory: engineFactory, 211 defaultEngine: defaultEngine, 212 lookbackDeltaCreate: lookbackDeltaCreate, 213 queryableCreate: c, 214 gate: gate, 215 ruleGroups: ruleGroups, 216 targets: targets, 217 metadatas: metadatas, 218 exemplars: exemplars, 219 enableAutodownsampling: enableAutodownsampling, 220 enableQueryPartialResponse: enableQueryPartialResponse, 221 enableRulePartialResponse: enableRulePartialResponse, 222 enableTargetPartialResponse: enableTargetPartialResponse, 223 enableMetricMetadataPartialResponse: enableMetricMetadataPartialResponse, 224 enableExemplarPartialResponse: enableExemplarPartialResponse, 225 enableQueryPushdown: enableQueryPushdown, 226 replicaLabels: replicaLabels, 227 endpointStatus: endpointStatus, 228 defaultRangeQueryStep: defaultRangeQueryStep, 229 defaultInstantQueryMaxSourceResolution: defaultInstantQueryMaxSourceResolution, 230 defaultMetadataTimeRange: defaultMetadataTimeRange, 231 disableCORS: disableCORS, 232 seriesStatsAggregatorFactory: statsAggregatorFactory, 233 tenantHeader: tenantHeader, 234 defaultTenant: defaultTenant, 235 tenantCertField: tenantCertField, 236 237 queryRangeHist: promauto.With(reg).NewHistogram(prometheus.HistogramOpts{ 238 Name: "thanos_query_range_requested_timespan_duration_seconds", 239 Help: "A histogram of the query range window in seconds", 240 Buckets: prometheus.ExponentialBuckets(15*60, 2, 12), 241 }), 242 } 243 } 244 245 // Register the API's endpoints in the given router. 246 func (qapi *QueryAPI) Register(r *route.Router, tracer opentracing.Tracer, logger log.Logger, ins extpromhttp.InstrumentationMiddleware, logMiddleware *logging.HTTPServerMiddleware) { 247 qapi.baseAPI.Register(r, tracer, logger, ins, logMiddleware) 248 249 instr := api.GetInstr(tracer, logger, ins, logMiddleware, qapi.disableCORS) 250 251 r.Get("/query", instr("query", qapi.query)) 252 r.Post("/query", instr("query", qapi.query)) 253 254 r.Get("/query_range", instr("query_range", qapi.queryRange)) 255 r.Post("/query_range", instr("query_range", qapi.queryRange)) 256 257 r.Get("/label/:name/values", instr("label_values", qapi.labelValues)) 258 259 r.Get("/series", instr("series", qapi.series)) 260 r.Post("/series", instr("series", qapi.series)) 261 262 r.Get("/labels", instr("label_names", qapi.labelNames)) 263 r.Post("/labels", instr("label_names", qapi.labelNames)) 264 265 r.Get("/stores", instr("stores", qapi.stores)) 266 267 r.Get("/alerts", instr("alerts", NewAlertsHandler(qapi.ruleGroups, qapi.enableRulePartialResponse))) 268 r.Get("/rules", instr("rules", NewRulesHandler(qapi.ruleGroups, qapi.enableRulePartialResponse))) 269 270 r.Get("/targets", instr("targets", NewTargetsHandler(qapi.targets, qapi.enableTargetPartialResponse))) 271 272 r.Get("/metadata", instr("metadata", NewMetricMetadataHandler(qapi.metadatas, qapi.enableMetricMetadataPartialResponse))) 273 274 r.Get("/query_exemplars", instr("exemplars", NewExemplarsHandler(qapi.exemplars, qapi.enableExemplarPartialResponse))) 275 r.Post("/query_exemplars", instr("exemplars", NewExemplarsHandler(qapi.exemplars, qapi.enableExemplarPartialResponse))) 276 } 277 278 type queryData struct { 279 ResultType parser.ValueType `json:"resultType"` 280 Result parser.Value `json:"result"` 281 Stats stats.QueryStats `json:"stats,omitempty"` 282 // Additional Thanos Response field. 283 QueryExplanation *engine.ExplainOutputNode `json:"explanation,omitempty"` 284 Warnings []error `json:"warnings,omitempty"` 285 } 286 287 func (qapi *QueryAPI) parseEnableDedupParam(r *http.Request) (enableDeduplication bool, _ *api.ApiError) { 288 enableDeduplication = true 289 290 if val := r.FormValue(DedupParam); val != "" { 291 var err error 292 enableDeduplication, err = strconv.ParseBool(val) 293 if err != nil { 294 return false, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Wrapf(err, "'%s' parameter", DedupParam)} 295 } 296 } 297 return enableDeduplication, nil 298 } 299 300 func (qapi *QueryAPI) parseEngineParam(r *http.Request) (queryEngine v1.QueryEngine, _ *api.ApiError) { 301 var engine v1.QueryEngine 302 303 param := PromqlEngineType(r.FormValue("engine")) 304 if param == "" { 305 param = qapi.defaultEngine 306 } 307 308 switch param { 309 case PromqlEnginePrometheus: 310 engine = qapi.engineFactory.GetPrometheusEngine() 311 case PromqlEngineThanos: 312 engine = qapi.engineFactory.GetThanosEngine() 313 default: 314 return nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("'%s' bad engine", param)} 315 } 316 317 return engine, nil 318 } 319 320 func (qapi *QueryAPI) parseReplicaLabelsParam(r *http.Request) (replicaLabels []string, _ *api.ApiError) { 321 if err := r.ParseForm(); err != nil { 322 return nil, &api.ApiError{Typ: api.ErrorInternal, Err: errors.Wrap(err, "parse form")} 323 } 324 325 replicaLabels = qapi.replicaLabels 326 // Overwrite the cli flag when provided as a query parameter. 327 if len(r.Form[ReplicaLabelsParam]) > 0 { 328 replicaLabels = r.Form[ReplicaLabelsParam] 329 } 330 331 return replicaLabels, nil 332 } 333 334 func (qapi *QueryAPI) parseStoreDebugMatchersParam(r *http.Request) (storeMatchers [][]*labels.Matcher, _ *api.ApiError) { 335 if err := r.ParseForm(); err != nil { 336 return nil, &api.ApiError{Typ: api.ErrorInternal, Err: errors.Wrap(err, "parse form")} 337 } 338 339 for _, s := range r.Form[StoreMatcherParam] { 340 matchers, err := parser.ParseMetricSelector(s) 341 if err != nil { 342 return nil, &api.ApiError{Typ: api.ErrorBadData, Err: err} 343 } 344 storeMatchers = append(storeMatchers, matchers) 345 } 346 347 return storeMatchers, nil 348 } 349 350 func (qapi *QueryAPI) parseLookbackDeltaParam(r *http.Request) (time.Duration, *api.ApiError) { 351 // Overwrite the cli flag when provided as a query parameter. 352 if val := r.FormValue(LookbackDeltaParam); val != "" { 353 var err error 354 lookbackDelta, err := parseDuration(val) 355 if err != nil { 356 return 0, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Wrapf(err, "'%s' parameter", LookbackDeltaParam)} 357 } 358 return lookbackDelta, nil 359 } 360 // If duration 0 is returned, lookback delta is taken from engine config. 361 return time.Duration(0), nil 362 } 363 364 func (qapi *QueryAPI) parseDownsamplingParamMillis(r *http.Request, defaultVal time.Duration) (maxResolutionMillis int64, _ *api.ApiError) { 365 maxSourceResolution := 0 * time.Second 366 367 val := r.FormValue(MaxSourceResolutionParam) 368 if qapi.enableAutodownsampling || (val == "auto") { 369 maxSourceResolution = defaultVal 370 } 371 if val != "" && val != "auto" { 372 var err error 373 maxSourceResolution, err = parseDuration(val) 374 if err != nil { 375 return 0, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Wrapf(err, "'%s' parameter", MaxSourceResolutionParam)} 376 } 377 } 378 379 if maxSourceResolution < 0 { 380 return 0, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("negative '%s' is not accepted. Try a positive integer", MaxSourceResolutionParam)} 381 } 382 383 return int64(maxSourceResolution / time.Millisecond), nil 384 } 385 386 func (qapi *QueryAPI) parsePartialResponseParam(r *http.Request, defaultEnablePartialResponse bool) (enablePartialResponse bool, _ *api.ApiError) { 387 // Overwrite the cli flag when provided as a query parameter. 388 if val := r.FormValue(PartialResponseParam); val != "" { 389 var err error 390 defaultEnablePartialResponse, err = strconv.ParseBool(val) 391 if err != nil { 392 return false, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Wrapf(err, "'%s' parameter", PartialResponseParam)} 393 } 394 } 395 return defaultEnablePartialResponse, nil 396 } 397 398 func (qapi *QueryAPI) parseStep(r *http.Request, defaultRangeQueryStep time.Duration, rangeSeconds int64) (time.Duration, *api.ApiError) { 399 // Overwrite the cli flag when provided as a query parameter. 400 if val := r.FormValue(Step); val != "" { 401 var err error 402 defaultRangeQueryStep, err = parseDuration(val) 403 if err != nil { 404 return 0, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Wrapf(err, "'%s' parameter", Step)} 405 } 406 return defaultRangeQueryStep, nil 407 } 408 // Default step is used this way to make it consistent with UI. 409 d := time.Duration(math.Max(float64(rangeSeconds/250), float64(defaultRangeQueryStep/time.Second))) * time.Second 410 return d, nil 411 } 412 413 func (qapi *QueryAPI) parseShardInfo(r *http.Request) (*storepb.ShardInfo, *api.ApiError) { 414 data := r.FormValue(ShardInfoParam) 415 if data == "" { 416 return nil, nil 417 } 418 419 if len(data) == 0 { 420 return nil, nil 421 } 422 423 var info storepb.ShardInfo 424 if err := json.Unmarshal([]byte(data), &info); err != nil { 425 return nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Wrapf(err, "could not unmarshal parameter %s", ShardInfoParam)} 426 } 427 428 return &info, nil 429 } 430 431 func (qapi *QueryAPI) parseQueryExplainParam(r *http.Request, query promql.Query) (*engine.ExplainOutputNode, *api.ApiError) { 432 var explanation *engine.ExplainOutputNode 433 434 if val := r.FormValue(QueryExplainParam); val != "" { 435 var err error 436 enableExplanation, err := strconv.ParseBool(val) 437 if err != nil { 438 return explanation, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Wrapf(err, "'%s' parameter", QueryExplainParam)} 439 } 440 if enableExplanation { 441 if eq, ok := query.(engine.ExplainableQuery); ok { 442 explanation = eq.Explain() 443 } else { 444 return explanation, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("Query not explainable")} 445 } 446 } 447 } 448 449 return explanation, nil 450 } 451 452 func (qapi *QueryAPI) query(r *http.Request) (interface{}, []error, *api.ApiError, func()) { 453 ts, err := parseTimeParam(r, "time", qapi.baseAPI.Now()) 454 if err != nil { 455 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 456 } 457 458 ctx := r.Context() 459 if to := r.FormValue("timeout"); to != "" { 460 var cancel context.CancelFunc 461 timeout, err := parseDuration(to) 462 if err != nil { 463 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 464 } 465 466 ctx, cancel = context.WithTimeout(ctx, timeout) 467 defer cancel() 468 } 469 470 enableDedup, apiErr := qapi.parseEnableDedupParam(r) 471 if apiErr != nil { 472 return nil, nil, apiErr, func() {} 473 } 474 475 replicaLabels, apiErr := qapi.parseReplicaLabelsParam(r) 476 if apiErr != nil { 477 return nil, nil, apiErr, func() {} 478 } 479 480 storeDebugMatchers, apiErr := qapi.parseStoreDebugMatchersParam(r) 481 if apiErr != nil { 482 return nil, nil, apiErr, func() {} 483 } 484 485 enablePartialResponse, apiErr := qapi.parsePartialResponseParam(r, qapi.enableQueryPartialResponse) 486 if apiErr != nil { 487 return nil, nil, apiErr, func() {} 488 } 489 490 maxSourceResolution, apiErr := qapi.parseDownsamplingParamMillis(r, qapi.defaultInstantQueryMaxSourceResolution) 491 if apiErr != nil { 492 return nil, nil, apiErr, func() {} 493 } 494 495 shardInfo, apiErr := qapi.parseShardInfo(r) 496 if apiErr != nil { 497 return nil, nil, apiErr, func() {} 498 } 499 500 engine, apiErr := qapi.parseEngineParam(r) 501 if apiErr != nil { 502 return nil, nil, apiErr, func() {} 503 } 504 505 lookbackDelta := qapi.lookbackDeltaCreate(maxSourceResolution) 506 // Get custom lookback delta from request. 507 lookbackDeltaFromReq, apiErr := qapi.parseLookbackDeltaParam(r) 508 if apiErr != nil { 509 return nil, nil, apiErr, func() {} 510 } 511 if lookbackDeltaFromReq > 0 { 512 lookbackDelta = lookbackDeltaFromReq 513 } 514 515 tenant, err := tenancy.GetTenantFromHTTP(r, qapi.tenantHeader, qapi.defaultTenant, qapi.tenantCertField) 516 if err != nil { 517 apiErr = &api.ApiError{Typ: api.ErrorBadData, Err: err} 518 return nil, nil, apiErr, func() {} 519 } 520 ctx = context.WithValue(ctx, tenancy.TenantKey, tenant) 521 522 // We are starting promQL tracing span here, because we have no control over promQL code. 523 span, ctx := tracing.StartSpan(ctx, "promql_instant_query") 524 defer span.Finish() 525 526 var seriesStats []storepb.SeriesStatsCounter 527 qry, err := engine.NewInstantQuery( 528 ctx, 529 qapi.queryableCreate( 530 enableDedup, 531 replicaLabels, 532 storeDebugMatchers, 533 maxSourceResolution, 534 enablePartialResponse, 535 qapi.enableQueryPushdown, 536 false, 537 shardInfo, 538 query.NewAggregateStatsReporter(&seriesStats), 539 ), 540 promql.NewPrometheusQueryOpts(false, lookbackDelta), 541 r.FormValue("query"), 542 ts, 543 ) 544 545 if err != nil { 546 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 547 } 548 549 explanation, apiErr := qapi.parseQueryExplainParam(r, qry) 550 if apiErr != nil { 551 return nil, nil, apiErr, func() {} 552 } 553 554 tracing.DoInSpan(ctx, "query_gate_ismyturn", func(ctx context.Context) { 555 err = qapi.gate.Start(ctx) 556 }) 557 if err != nil { 558 return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: err}, qry.Close 559 } 560 defer qapi.gate.Done() 561 562 beforeRange := time.Now() 563 res := qry.Exec(ctx) 564 if res.Err != nil { 565 switch res.Err.(type) { 566 case promql.ErrQueryCanceled: 567 return nil, nil, &api.ApiError{Typ: api.ErrorCanceled, Err: res.Err}, qry.Close 568 case promql.ErrQueryTimeout: 569 return nil, nil, &api.ApiError{Typ: api.ErrorTimeout, Err: res.Err}, qry.Close 570 case promql.ErrStorage: 571 return nil, nil, &api.ApiError{Typ: api.ErrorInternal, Err: res.Err}, qry.Close 572 } 573 return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: res.Err}, qry.Close 574 } 575 576 aggregator := qapi.seriesStatsAggregatorFactory.NewAggregator() 577 for i := range seriesStats { 578 aggregator.Aggregate(seriesStats[i]) 579 } 580 aggregator.Observe(time.Since(beforeRange).Seconds()) 581 582 // Optional stats field in response if parameter "stats" is not empty. 583 var qs stats.QueryStats 584 if r.FormValue(Stats) != "" { 585 qs = stats.NewQueryStats(qry.Stats()) 586 } 587 return &queryData{ 588 ResultType: res.Value.Type(), 589 Result: res.Value, 590 Stats: qs, 591 QueryExplanation: explanation, 592 }, res.Warnings, nil, qry.Close 593 } 594 595 func (qapi *QueryAPI) queryRange(r *http.Request) (interface{}, []error, *api.ApiError, func()) { 596 start, err := parseTime(r.FormValue("start")) 597 if err != nil { 598 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 599 } 600 end, err := parseTime(r.FormValue("end")) 601 if err != nil { 602 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 603 } 604 if end.Before(start) { 605 err := errors.New("end timestamp must not be before start time") 606 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 607 } 608 609 step, apiErr := qapi.parseStep(r, qapi.defaultRangeQueryStep, int64(end.Sub(start)/time.Second)) 610 if apiErr != nil { 611 return nil, nil, apiErr, func() {} 612 } 613 614 if step <= 0 { 615 err := errors.New("zero or negative query resolution step widths are not accepted. Try a positive integer") 616 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 617 } 618 619 // For safety, limit the number of returned points per timeseries. 620 // This is sufficient for 60s resolution for a week or 1h resolution for a year. 621 if end.Sub(start)/step > 11000 { 622 err := errors.New("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)") 623 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 624 } 625 626 ctx := r.Context() 627 if to := r.FormValue("timeout"); to != "" { 628 var cancel context.CancelFunc 629 timeout, err := parseDuration(to) 630 if err != nil { 631 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 632 } 633 634 ctx, cancel = context.WithTimeout(ctx, timeout) 635 defer cancel() 636 } 637 638 enableDedup, apiErr := qapi.parseEnableDedupParam(r) 639 if apiErr != nil { 640 return nil, nil, apiErr, func() {} 641 } 642 643 replicaLabels, apiErr := qapi.parseReplicaLabelsParam(r) 644 if apiErr != nil { 645 return nil, nil, apiErr, func() {} 646 } 647 648 storeDebugMatchers, apiErr := qapi.parseStoreDebugMatchersParam(r) 649 if apiErr != nil { 650 return nil, nil, apiErr, func() {} 651 } 652 653 // If no max_source_resolution is specified fit at least 5 samples between steps. 654 maxSourceResolution, apiErr := qapi.parseDownsamplingParamMillis(r, step/5) 655 if apiErr != nil { 656 return nil, nil, apiErr, func() {} 657 } 658 659 enablePartialResponse, apiErr := qapi.parsePartialResponseParam(r, qapi.enableQueryPartialResponse) 660 if apiErr != nil { 661 return nil, nil, apiErr, func() {} 662 } 663 664 shardInfo, apiErr := qapi.parseShardInfo(r) 665 if apiErr != nil { 666 return nil, nil, apiErr, func() {} 667 } 668 669 engine, apiErr := qapi.parseEngineParam(r) 670 if apiErr != nil { 671 return nil, nil, apiErr, func() {} 672 } 673 674 lookbackDelta := qapi.lookbackDeltaCreate(maxSourceResolution) 675 // Get custom lookback delta from request. 676 lookbackDeltaFromReq, apiErr := qapi.parseLookbackDeltaParam(r) 677 if apiErr != nil { 678 return nil, nil, apiErr, func() {} 679 } 680 if lookbackDeltaFromReq > 0 { 681 lookbackDelta = lookbackDeltaFromReq 682 } 683 684 tenant, err := tenancy.GetTenantFromHTTP(r, qapi.tenantHeader, qapi.defaultTenant, qapi.tenantCertField) 685 if err != nil { 686 apiErr = &api.ApiError{Typ: api.ErrorBadData, Err: err} 687 return nil, nil, apiErr, func() {} 688 } 689 ctx = context.WithValue(ctx, tenancy.TenantKey, tenant) 690 691 // Record the query range requested. 692 qapi.queryRangeHist.Observe(end.Sub(start).Seconds()) 693 694 // We are starting promQL tracing span here, because we have no control over promQL code. 695 span, ctx := tracing.StartSpan(ctx, "promql_range_query") 696 defer span.Finish() 697 698 var seriesStats []storepb.SeriesStatsCounter 699 qry, err := engine.NewRangeQuery( 700 ctx, 701 qapi.queryableCreate( 702 enableDedup, 703 replicaLabels, 704 storeDebugMatchers, 705 maxSourceResolution, 706 enablePartialResponse, 707 qapi.enableQueryPushdown, 708 false, 709 shardInfo, 710 query.NewAggregateStatsReporter(&seriesStats), 711 ), 712 promql.NewPrometheusQueryOpts(false, lookbackDelta), 713 r.FormValue("query"), 714 start, 715 end, 716 step, 717 ) 718 if err != nil { 719 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 720 } 721 722 explanation, apiErr := qapi.parseQueryExplainParam(r, qry) 723 if apiErr != nil { 724 return nil, nil, apiErr, func() {} 725 } 726 727 tracing.DoInSpan(ctx, "query_gate_ismyturn", func(ctx context.Context) { 728 err = qapi.gate.Start(ctx) 729 }) 730 if err != nil { 731 return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: err}, qry.Close 732 } 733 defer qapi.gate.Done() 734 735 beforeRange := time.Now() 736 res := qry.Exec(ctx) 737 if res.Err != nil { 738 switch res.Err.(type) { 739 case promql.ErrQueryCanceled: 740 return nil, nil, &api.ApiError{Typ: api.ErrorCanceled, Err: res.Err}, qry.Close 741 case promql.ErrQueryTimeout: 742 return nil, nil, &api.ApiError{Typ: api.ErrorTimeout, Err: res.Err}, qry.Close 743 } 744 return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: res.Err}, qry.Close 745 } 746 aggregator := qapi.seriesStatsAggregatorFactory.NewAggregator() 747 for i := range seriesStats { 748 aggregator.Aggregate(seriesStats[i]) 749 } 750 aggregator.Observe(time.Since(beforeRange).Seconds()) 751 752 // Optional stats field in response if parameter "stats" is not empty. 753 var qs stats.QueryStats 754 if r.FormValue(Stats) != "" { 755 qs = stats.NewQueryStats(qry.Stats()) 756 } 757 return &queryData{ 758 ResultType: res.Value.Type(), 759 Result: res.Value, 760 Stats: qs, 761 QueryExplanation: explanation, 762 }, res.Warnings, nil, qry.Close 763 } 764 765 func (qapi *QueryAPI) labelValues(r *http.Request) (interface{}, []error, *api.ApiError, func()) { 766 ctx := r.Context() 767 name := route.Param(ctx, "name") 768 769 if !model.LabelNameRE.MatchString(name) { 770 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("invalid label name: %q", name)}, func() {} 771 } 772 773 start, end, err := parseMetadataTimeRange(r, qapi.defaultMetadataTimeRange) 774 if err != nil { 775 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 776 } 777 778 enablePartialResponse, apiErr := qapi.parsePartialResponseParam(r, qapi.enableQueryPartialResponse) 779 if apiErr != nil { 780 return nil, nil, apiErr, func() {} 781 } 782 783 storeDebugMatchers, apiErr := qapi.parseStoreDebugMatchersParam(r) 784 if apiErr != nil { 785 return nil, nil, apiErr, func() {} 786 } 787 788 var matcherSets [][]*labels.Matcher 789 for _, s := range r.Form[MatcherParam] { 790 matchers, err := parser.ParseMetricSelector(s) 791 if err != nil { 792 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 793 } 794 matcherSets = append(matcherSets, matchers) 795 } 796 797 tenant, err := tenancy.GetTenantFromHTTP(r, qapi.tenantHeader, qapi.defaultTenant, qapi.tenantCertField) 798 if err != nil { 799 apiErr = &api.ApiError{Typ: api.ErrorBadData, Err: err} 800 return nil, nil, apiErr, func() {} 801 } 802 ctx = context.WithValue(ctx, tenancy.TenantKey, tenant) 803 804 q, err := qapi.queryableCreate( 805 true, 806 nil, 807 storeDebugMatchers, 808 0, 809 enablePartialResponse, 810 qapi.enableQueryPushdown, 811 true, 812 nil, 813 query.NoopSeriesStatsReporter, 814 ).Querier(ctx, timestamp.FromTime(start), timestamp.FromTime(end)) 815 if err != nil { 816 return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: err}, func() {} 817 } 818 defer runutil.CloseWithLogOnErr(qapi.logger, q, "queryable labelValues") 819 820 var ( 821 vals []string 822 warnings storage.Warnings 823 ) 824 if len(matcherSets) > 0 { 825 var callWarnings storage.Warnings 826 labelValuesSet := make(map[string]struct{}) 827 for _, matchers := range matcherSets { 828 vals, callWarnings, err = q.LabelValues(name, matchers...) 829 if err != nil { 830 return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: err}, func() {} 831 } 832 warnings = append(warnings, callWarnings...) 833 for _, val := range vals { 834 labelValuesSet[val] = struct{}{} 835 } 836 } 837 838 vals = make([]string, 0, len(labelValuesSet)) 839 for val := range labelValuesSet { 840 vals = append(vals, val) 841 } 842 sort.Strings(vals) 843 } else { 844 vals, warnings, err = q.LabelValues(name) 845 if err != nil { 846 return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: err}, func() {} 847 } 848 } 849 850 if vals == nil { 851 vals = make([]string, 0) 852 } 853 854 return vals, warnings, nil, func() {} 855 } 856 857 func (qapi *QueryAPI) series(r *http.Request) (interface{}, []error, *api.ApiError, func()) { 858 if err := r.ParseForm(); err != nil { 859 return nil, nil, &api.ApiError{Typ: api.ErrorInternal, Err: errors.Wrap(err, "parse form")}, func() {} 860 } 861 862 if len(r.Form[MatcherParam]) == 0 { 863 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.New("no match[] parameter provided")}, func() {} 864 } 865 866 start, end, err := parseMetadataTimeRange(r, qapi.defaultMetadataTimeRange) 867 if err != nil { 868 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 869 } 870 871 var matcherSets [][]*labels.Matcher 872 for _, s := range r.Form[MatcherParam] { 873 matchers, err := parser.ParseMetricSelector(s) 874 if err != nil { 875 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 876 } 877 matcherSets = append(matcherSets, matchers) 878 } 879 880 enableDedup, apiErr := qapi.parseEnableDedupParam(r) 881 if apiErr != nil { 882 return nil, nil, apiErr, func() {} 883 } 884 885 replicaLabels, apiErr := qapi.parseReplicaLabelsParam(r) 886 if apiErr != nil { 887 return nil, nil, apiErr, func() {} 888 } 889 890 storeDebugMatchers, apiErr := qapi.parseStoreDebugMatchersParam(r) 891 if apiErr != nil { 892 return nil, nil, apiErr, func() {} 893 } 894 895 enablePartialResponse, apiErr := qapi.parsePartialResponseParam(r, qapi.enableQueryPartialResponse) 896 if apiErr != nil { 897 return nil, nil, apiErr, func() {} 898 } 899 900 tenant, err := tenancy.GetTenantFromHTTP(r, qapi.tenantHeader, qapi.defaultTenant, "") 901 if err != nil { 902 apiErr = &api.ApiError{Typ: api.ErrorBadData, Err: err} 903 return nil, nil, apiErr, func() {} 904 } 905 ctx := context.WithValue(r.Context(), tenancy.TenantKey, tenant) 906 907 q, err := qapi.queryableCreate( 908 enableDedup, 909 replicaLabels, 910 storeDebugMatchers, 911 math.MaxInt64, 912 enablePartialResponse, 913 qapi.enableQueryPushdown, 914 true, 915 nil, 916 query.NoopSeriesStatsReporter, 917 ).Querier(ctx, timestamp.FromTime(start), timestamp.FromTime(end)) 918 919 if err != nil { 920 return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: err}, func() {} 921 } 922 defer runutil.CloseWithLogOnErr(qapi.logger, q, "queryable series") 923 924 var ( 925 metrics = []labels.Labels{} 926 sets []storage.SeriesSet 927 ) 928 for _, mset := range matcherSets { 929 sets = append(sets, q.Select(false, nil, mset...)) 930 } 931 932 set := storage.NewMergeSeriesSet(sets, storage.ChainedSeriesMerge) 933 for set.Next() { 934 metrics = append(metrics, set.At().Labels()) 935 } 936 if set.Err() != nil { 937 return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: set.Err()}, func() {} 938 } 939 return metrics, set.Warnings(), nil, func() {} 940 } 941 942 func (qapi *QueryAPI) labelNames(r *http.Request) (interface{}, []error, *api.ApiError, func()) { 943 start, end, err := parseMetadataTimeRange(r, qapi.defaultMetadataTimeRange) 944 if err != nil { 945 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 946 } 947 948 enablePartialResponse, apiErr := qapi.parsePartialResponseParam(r, qapi.enableQueryPartialResponse) 949 if apiErr != nil { 950 return nil, nil, apiErr, func() {} 951 } 952 953 storeDebugMatchers, apiErr := qapi.parseStoreDebugMatchersParam(r) 954 if apiErr != nil { 955 return nil, nil, apiErr, func() {} 956 } 957 958 var matcherSets [][]*labels.Matcher 959 for _, s := range r.Form[MatcherParam] { 960 matchers, err := parser.ParseMetricSelector(s) 961 if err != nil { 962 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 963 } 964 matcherSets = append(matcherSets, matchers) 965 } 966 967 tenant, err := tenancy.GetTenantFromHTTP(r, qapi.tenantHeader, qapi.defaultTenant, "") 968 if err != nil { 969 apiErr = &api.ApiError{Typ: api.ErrorBadData, Err: err} 970 return nil, nil, apiErr, func() {} 971 } 972 ctx := context.WithValue(r.Context(), tenancy.TenantKey, tenant) 973 974 q, err := qapi.queryableCreate( 975 true, 976 nil, 977 storeDebugMatchers, 978 0, 979 enablePartialResponse, 980 qapi.enableQueryPushdown, 981 true, 982 nil, 983 query.NoopSeriesStatsReporter, 984 ).Querier(ctx, timestamp.FromTime(start), timestamp.FromTime(end)) 985 if err != nil { 986 return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: err}, func() {} 987 } 988 defer runutil.CloseWithLogOnErr(qapi.logger, q, "queryable labelNames") 989 990 var ( 991 names []string 992 warnings storage.Warnings 993 ) 994 995 if len(matcherSets) > 0 { 996 var callWarnings storage.Warnings 997 labelNamesSet := make(map[string]struct{}) 998 for _, matchers := range matcherSets { 999 names, callWarnings, err = q.LabelNames(matchers...) 1000 if err != nil { 1001 return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: err}, func() {} 1002 } 1003 warnings = append(warnings, callWarnings...) 1004 for _, val := range names { 1005 labelNamesSet[val] = struct{}{} 1006 } 1007 } 1008 1009 names = make([]string, 0, len(labelNamesSet)) 1010 for name := range labelNamesSet { 1011 names = append(names, name) 1012 } 1013 sort.Strings(names) 1014 } else { 1015 names, warnings, err = q.LabelNames() 1016 } 1017 1018 if err != nil { 1019 return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: err}, func() {} 1020 } 1021 if names == nil { 1022 names = make([]string, 0) 1023 } 1024 1025 return names, warnings, nil, func() {} 1026 } 1027 1028 func (qapi *QueryAPI) stores(_ *http.Request) (interface{}, []error, *api.ApiError, func()) { 1029 statuses := make(map[string][]query.EndpointStatus) 1030 for _, status := range qapi.endpointStatus() { 1031 // Don't consider an endpoint if we cannot retrieve component type. 1032 if status.ComponentType == nil { 1033 continue 1034 } 1035 statuses[status.ComponentType.String()] = append(statuses[status.ComponentType.String()], status) 1036 } 1037 return statuses, nil, nil, func() {} 1038 } 1039 1040 // NewTargetsHandler created handler compatible with HTTP /api/v1/targets https://prometheus.io/docs/prometheus/latest/querying/api/#targets 1041 // which uses gRPC Unary Targets API. 1042 func NewTargetsHandler(client targets.UnaryClient, enablePartialResponse bool) func(*http.Request) (interface{}, []error, *api.ApiError, func()) { 1043 ps := storepb.PartialResponseStrategy_ABORT 1044 if enablePartialResponse { 1045 ps = storepb.PartialResponseStrategy_WARN 1046 } 1047 1048 return func(r *http.Request) (interface{}, []error, *api.ApiError, func()) { 1049 stateParam := r.URL.Query().Get("state") 1050 state, ok := targetspb.TargetsRequest_State_value[strings.ToUpper(stateParam)] 1051 if !ok { 1052 if stateParam != "" { 1053 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("invalid targets parameter state='%v'", stateParam)}, func() {} 1054 } 1055 state = int32(targetspb.TargetsRequest_ANY) 1056 } 1057 1058 req := &targetspb.TargetsRequest{ 1059 State: targetspb.TargetsRequest_State(state), 1060 PartialResponseStrategy: ps, 1061 } 1062 1063 t, warnings, err := client.Targets(r.Context(), req) 1064 if err != nil { 1065 return nil, nil, &api.ApiError{Typ: api.ErrorInternal, Err: errors.Wrap(err, "retrieving targets")}, func() {} 1066 } 1067 1068 return t, warnings, nil, func() {} 1069 } 1070 } 1071 1072 // NewAlertsHandler created handler compatible with HTTP /api/v1/alerts https://prometheus.io/docs/prometheus/latest/querying/api/#alerts 1073 // which uses gRPC Unary Rules API (Rules API works for both /alerts and /rules). 1074 func NewAlertsHandler(client rules.UnaryClient, enablePartialResponse bool) func(*http.Request) (interface{}, []error, *api.ApiError, func()) { 1075 ps := storepb.PartialResponseStrategy_ABORT 1076 if enablePartialResponse { 1077 ps = storepb.PartialResponseStrategy_WARN 1078 } 1079 1080 return func(r *http.Request) (interface{}, []error, *api.ApiError, func()) { 1081 span, ctx := tracing.StartSpan(r.Context(), "receive_http_request") 1082 defer span.Finish() 1083 1084 var ( 1085 groups *rulespb.RuleGroups 1086 warnings storage.Warnings 1087 err error 1088 ) 1089 1090 // TODO(bwplotka): Allow exactly the same functionality as query API: passing replica, dedup and partial response as HTTP params as well. 1091 req := &rulespb.RulesRequest{ 1092 Type: rulespb.RulesRequest_ALERT, 1093 PartialResponseStrategy: ps, 1094 } 1095 tracing.DoInSpan(ctx, "retrieve_rules", func(ctx context.Context) { 1096 groups, warnings, err = client.Rules(ctx, req) 1097 }) 1098 if err != nil { 1099 return nil, nil, &api.ApiError{Typ: api.ErrorInternal, Err: errors.Errorf("error retrieving rules: %v", err)}, func() {} 1100 } 1101 1102 var resp struct { 1103 Alerts []*rulespb.AlertInstance `json:"alerts"` 1104 } 1105 for _, g := range groups.Groups { 1106 for _, r := range g.Rules { 1107 a := r.GetAlert() 1108 if a == nil { 1109 continue 1110 } 1111 resp.Alerts = append(resp.Alerts, a.Alerts...) 1112 } 1113 } 1114 return resp, warnings, nil, func() {} 1115 } 1116 } 1117 1118 // NewRulesHandler created handler compatible with HTTP /api/v1/rules https://prometheus.io/docs/prometheus/latest/querying/api/#rules 1119 // which uses gRPC Unary Rules API. 1120 func NewRulesHandler(client rules.UnaryClient, enablePartialResponse bool) func(*http.Request) (interface{}, []error, *api.ApiError, func()) { 1121 ps := storepb.PartialResponseStrategy_ABORT 1122 if enablePartialResponse { 1123 ps = storepb.PartialResponseStrategy_WARN 1124 } 1125 1126 return func(r *http.Request) (interface{}, []error, *api.ApiError, func()) { 1127 span, ctx := tracing.StartSpan(r.Context(), "receive_http_request") 1128 defer span.Finish() 1129 1130 var ( 1131 groups *rulespb.RuleGroups 1132 warnings storage.Warnings 1133 err error 1134 ) 1135 1136 typeParam := r.URL.Query().Get("type") 1137 typ, ok := rulespb.RulesRequest_Type_value[strings.ToUpper(typeParam)] 1138 if !ok { 1139 if typeParam != "" { 1140 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("invalid rules parameter type='%v'", typeParam)}, func() {} 1141 } 1142 typ = int32(rulespb.RulesRequest_ALL) 1143 } 1144 1145 if err := r.ParseForm(); err != nil { 1146 return nil, nil, &api.ApiError{Typ: api.ErrorInternal, Err: errors.Errorf("error parsing request form='%v'", MatcherParam)}, func() {} 1147 } 1148 1149 // TODO(bwplotka): Allow exactly the same functionality as query API: passing replica, dedup and partial response as HTTP params as well. 1150 req := &rulespb.RulesRequest{ 1151 Type: rulespb.RulesRequest_Type(typ), 1152 PartialResponseStrategy: ps, 1153 MatcherString: r.Form[MatcherParam], 1154 } 1155 tracing.DoInSpan(ctx, "retrieve_rules", func(ctx context.Context) { 1156 groups, warnings, err = client.Rules(ctx, req) 1157 }) 1158 if err != nil { 1159 return nil, nil, &api.ApiError{Typ: api.ErrorInternal, Err: errors.Errorf("error retrieving rules: %v", err)}, func() {} 1160 } 1161 return groups, warnings, nil, func() {} 1162 } 1163 } 1164 1165 // NewExemplarsHandler creates handler compatible with HTTP /api/v1/query_exemplars https://prometheus.io/docs/prometheus/latest/querying/api/#querying-exemplars 1166 // which uses gRPC Unary Exemplars API. 1167 func NewExemplarsHandler(client exemplars.UnaryClient, enablePartialResponse bool) func(*http.Request) (interface{}, []error, *api.ApiError, func()) { 1168 ps := storepb.PartialResponseStrategy_ABORT 1169 if enablePartialResponse { 1170 ps = storepb.PartialResponseStrategy_WARN 1171 } 1172 1173 return func(r *http.Request) (interface{}, []error, *api.ApiError, func()) { 1174 span, ctx := tracing.StartSpan(r.Context(), "exemplar_query_request") 1175 defer span.Finish() 1176 1177 var ( 1178 data []*exemplarspb.ExemplarData 1179 warnings storage.Warnings 1180 err error 1181 ) 1182 1183 start, err := parseTimeParam(r, "start", infMinTime) 1184 if err != nil { 1185 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 1186 } 1187 end, err := parseTimeParam(r, "end", infMaxTime) 1188 if err != nil { 1189 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 1190 } 1191 1192 req := &exemplarspb.ExemplarsRequest{ 1193 Start: timestamp.FromTime(start), 1194 End: timestamp.FromTime(end), 1195 Query: r.FormValue("query"), 1196 PartialResponseStrategy: ps, 1197 } 1198 1199 tracing.DoInSpan(ctx, "retrieve_exemplars", func(ctx context.Context) { 1200 data, warnings, err = client.Exemplars(ctx, req) 1201 }) 1202 1203 if err != nil { 1204 return nil, nil, &api.ApiError{Typ: api.ErrorInternal, Err: errors.Wrap(err, "retrieving exemplars")}, func() {} 1205 } 1206 return data, warnings, nil, func() {} 1207 } 1208 } 1209 1210 var ( 1211 infMinTime = time.Unix(math.MinInt64/1000+62135596801, 0) 1212 infMaxTime = time.Unix(math.MaxInt64/1000-62135596801, 999999999) 1213 ) 1214 1215 func parseMetadataTimeRange(r *http.Request, defaultMetadataTimeRange time.Duration) (time.Time, time.Time, error) { 1216 // If start and end time not specified as query parameter, we get the range from the beginning of time by default. 1217 var defaultStartTime, defaultEndTime time.Time 1218 if defaultMetadataTimeRange == 0 { 1219 defaultStartTime = infMinTime 1220 defaultEndTime = infMaxTime 1221 } else { 1222 now := time.Now() 1223 defaultStartTime = now.Add(-defaultMetadataTimeRange) 1224 defaultEndTime = now 1225 } 1226 1227 start, err := parseTimeParam(r, "start", defaultStartTime) 1228 if err != nil { 1229 return time.Time{}, time.Time{}, &api.ApiError{Typ: api.ErrorBadData, Err: err} 1230 } 1231 end, err := parseTimeParam(r, "end", defaultEndTime) 1232 if err != nil { 1233 return time.Time{}, time.Time{}, &api.ApiError{Typ: api.ErrorBadData, Err: err} 1234 } 1235 if end.Before(start) { 1236 return time.Time{}, time.Time{}, &api.ApiError{ 1237 Typ: api.ErrorBadData, 1238 Err: errors.New("end timestamp must not be before start time"), 1239 } 1240 } 1241 1242 return start, end, nil 1243 } 1244 1245 func parseTimeParam(r *http.Request, paramName string, defaultValue time.Time) (time.Time, error) { 1246 val := r.FormValue(paramName) 1247 if val == "" { 1248 return defaultValue, nil 1249 } 1250 result, err := parseTime(val) 1251 if err != nil { 1252 return time.Time{}, errors.Wrapf(err, "Invalid time value for '%s'", paramName) 1253 } 1254 return result, nil 1255 } 1256 1257 func parseTime(s string) (time.Time, error) { 1258 if t, err := strconv.ParseFloat(s, 64); err == nil { 1259 s, ns := math.Modf(t) 1260 ns = math.Round(ns*1000) / 1000 1261 return time.Unix(int64(s), int64(ns*float64(time.Second))), nil 1262 } 1263 if t, err := time.Parse(time.RFC3339Nano, s); err == nil { 1264 return t, nil 1265 } 1266 return time.Time{}, errors.Errorf("cannot parse %q to a valid timestamp", s) 1267 } 1268 1269 func parseDuration(s string) (time.Duration, error) { 1270 if d, err := strconv.ParseFloat(s, 64); err == nil { 1271 ts := d * float64(time.Second) 1272 if ts > float64(math.MaxInt64) || ts < float64(math.MinInt64) { 1273 return 0, errors.Errorf("cannot parse %q to a valid duration. It overflows int64", s) 1274 } 1275 return time.Duration(ts), nil 1276 } 1277 if d, err := model.ParseDuration(s); err == nil { 1278 return time.Duration(d), nil 1279 } 1280 return 0, errors.Errorf("cannot parse %q to a valid duration", s) 1281 } 1282 1283 // NewMetricMetadataHandler creates handler compatible with HTTP /api/v1/metadata https://prometheus.io/docs/prometheus/latest/querying/api/#querying-metric-metadata 1284 // which uses gRPC Unary Metadata API. 1285 func NewMetricMetadataHandler(client metadata.UnaryClient, enablePartialResponse bool) func(*http.Request) (interface{}, []error, *api.ApiError, func()) { 1286 ps := storepb.PartialResponseStrategy_ABORT 1287 if enablePartialResponse { 1288 ps = storepb.PartialResponseStrategy_WARN 1289 } 1290 1291 return func(r *http.Request) (interface{}, []error, *api.ApiError, func()) { 1292 span, ctx := tracing.StartSpan(r.Context(), "metadata_http_request") 1293 defer span.Finish() 1294 1295 var ( 1296 t map[string][]metadatapb.Meta 1297 warnings storage.Warnings 1298 err error 1299 ) 1300 1301 req := &metadatapb.MetricMetadataRequest{ 1302 // By default we use -1, which means no limit. 1303 Limit: -1, 1304 Metric: r.URL.Query().Get("metric"), 1305 PartialResponseStrategy: ps, 1306 } 1307 1308 limitStr := r.URL.Query().Get("limit") 1309 if limitStr != "" { 1310 limit, err := strconv.ParseInt(limitStr, 10, 32) 1311 if err != nil { 1312 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("invalid metric metadata limit='%v'", limit)}, func() {} 1313 } 1314 req.Limit = int32(limit) 1315 } 1316 1317 tracing.DoInSpan(ctx, "retrieve_metadata", func(ctx context.Context) { 1318 t, warnings, err = client.MetricMetadata(ctx, req) 1319 }) 1320 if err != nil { 1321 return nil, nil, &api.ApiError{Typ: api.ErrorInternal, Err: errors.Wrap(err, "retrieving metadata")}, func() {} 1322 } 1323 1324 return t, warnings, nil, func() {} 1325 } 1326 }