1// Copyright 2017 The Prometheus Authors 2// Licensed under the Apache License, Version 2.0 (the "License"); 3// you may not use this file except in compliance with the License. 4// You may obtain a copy of the License at 5// 6// http://www.apache.org/licenses/LICENSE-2.0 7// 8// Unless required by applicable law or agreed to in writing, software 9// distributed under the License is distributed on an "AS IS" BASIS, 10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11// See the License for the specific language governing permissions and 12// limitations under the License. 13 14// Package v1 provides bindings to the Prometheus HTTP API v1: 15// http://prometheus.io/docs/querying/api/ 16package v1 17 18import ( 19 "context" 20 "errors" 21 "fmt" 22 "math" 23 "net/http" 24 "net/url" 25 "strconv" 26 "strings" 27 "time" 28 "unsafe" 29 30 json "github.com/json-iterator/go" 31 32 "github.com/prometheus/common/model" 33 34 "github.com/prometheus/client_golang/api" 35) 36 37func init() { 38 json.RegisterTypeEncoderFunc("model.SamplePair", marshalPointJSON, marshalPointJSONIsEmpty) 39 json.RegisterTypeDecoderFunc("model.SamplePair", unMarshalPointJSON) 40} 41 42func unMarshalPointJSON(ptr unsafe.Pointer, iter *json.Iterator) { 43 p := (*model.SamplePair)(ptr) 44 if !iter.ReadArray() { 45 iter.ReportError("unmarshal model.SamplePair", "SamplePair must be [timestamp, value]") 46 return 47 } 48 t := iter.ReadNumber() 49 if err := p.Timestamp.UnmarshalJSON([]byte(t)); err != nil { 50 iter.ReportError("unmarshal model.SamplePair", err.Error()) 51 return 52 } 53 if !iter.ReadArray() { 54 iter.ReportError("unmarshal model.SamplePair", "SamplePair missing value") 55 return 56 } 57 58 f, err := strconv.ParseFloat(iter.ReadString(), 64) 59 if err != nil { 60 iter.ReportError("unmarshal model.SamplePair", err.Error()) 61 return 62 } 63 p.Value = model.SampleValue(f) 64 65 if iter.ReadArray() { 66 iter.ReportError("unmarshal model.SamplePair", "SamplePair has too many values, must be [timestamp, value]") 67 return 68 } 69} 70 71func marshalPointJSON(ptr unsafe.Pointer, stream *json.Stream) { 72 p := *((*model.SamplePair)(ptr)) 73 stream.WriteArrayStart() 74 // Write out the timestamp as a float divided by 1000. 75 // This is ~3x faster than converting to a float. 76 t := int64(p.Timestamp) 77 if t < 0 { 78 stream.WriteRaw(`-`) 79 t = -t 80 } 81 stream.WriteInt64(t / 1000) 82 fraction := t % 1000 83 if fraction != 0 { 84 stream.WriteRaw(`.`) 85 if fraction < 100 { 86 stream.WriteRaw(`0`) 87 } 88 if fraction < 10 { 89 stream.WriteRaw(`0`) 90 } 91 stream.WriteInt64(fraction) 92 } 93 stream.WriteMore() 94 stream.WriteRaw(`"`) 95 96 // Taken from https://github.com/json-iterator/go/blob/master/stream_float.go#L71 as a workaround 97 // to https://github.com/json-iterator/go/issues/365 (jsoniter, to follow json standard, doesn't allow inf/nan) 98 buf := stream.Buffer() 99 abs := math.Abs(float64(p.Value)) 100 fmt := byte('f') 101 // Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right. 102 if abs != 0 { 103 if abs < 1e-6 || abs >= 1e21 { 104 fmt = 'e' 105 } 106 } 107 buf = strconv.AppendFloat(buf, float64(p.Value), fmt, -1, 64) 108 stream.SetBuffer(buf) 109 110 stream.WriteRaw(`"`) 111 stream.WriteArrayEnd() 112 113} 114 115func marshalPointJSONIsEmpty(ptr unsafe.Pointer) bool { 116 return false 117} 118 119const ( 120 apiPrefix = "/api/v1" 121 122 epAlerts = apiPrefix + "/alerts" 123 epAlertManagers = apiPrefix + "/alertmanagers" 124 epQuery = apiPrefix + "/query" 125 epQueryRange = apiPrefix + "/query_range" 126 epQueryExemplars = apiPrefix + "/query_exemplars" 127 epLabels = apiPrefix + "/labels" 128 epLabelValues = apiPrefix + "/label/:name/values" 129 epSeries = apiPrefix + "/series" 130 epTargets = apiPrefix + "/targets" 131 epTargetsMetadata = apiPrefix + "/targets/metadata" 132 epMetadata = apiPrefix + "/metadata" 133 epRules = apiPrefix + "/rules" 134 epSnapshot = apiPrefix + "/admin/tsdb/snapshot" 135 epDeleteSeries = apiPrefix + "/admin/tsdb/delete_series" 136 epCleanTombstones = apiPrefix + "/admin/tsdb/clean_tombstones" 137 epConfig = apiPrefix + "/status/config" 138 epFlags = apiPrefix + "/status/flags" 139 epBuildinfo = apiPrefix + "/status/buildinfo" 140 epRuntimeinfo = apiPrefix + "/status/runtimeinfo" 141 epTSDB = apiPrefix + "/status/tsdb" 142) 143 144// AlertState models the state of an alert. 145type AlertState string 146 147// ErrorType models the different API error types. 148type ErrorType string 149 150// HealthStatus models the health status of a scrape target. 151type HealthStatus string 152 153// RuleType models the type of a rule. 154type RuleType string 155 156// RuleHealth models the health status of a rule. 157type RuleHealth string 158 159// MetricType models the type of a metric. 160type MetricType string 161 162const ( 163 // Possible values for AlertState. 164 AlertStateFiring AlertState = "firing" 165 AlertStateInactive AlertState = "inactive" 166 AlertStatePending AlertState = "pending" 167 168 // Possible values for ErrorType. 169 ErrBadData ErrorType = "bad_data" 170 ErrTimeout ErrorType = "timeout" 171 ErrCanceled ErrorType = "canceled" 172 ErrExec ErrorType = "execution" 173 ErrBadResponse ErrorType = "bad_response" 174 ErrServer ErrorType = "server_error" 175 ErrClient ErrorType = "client_error" 176 177 // Possible values for HealthStatus. 178 HealthGood HealthStatus = "up" 179 HealthUnknown HealthStatus = "unknown" 180 HealthBad HealthStatus = "down" 181 182 // Possible values for RuleType. 183 RuleTypeRecording RuleType = "recording" 184 RuleTypeAlerting RuleType = "alerting" 185 186 // Possible values for RuleHealth. 187 RuleHealthGood = "ok" 188 RuleHealthUnknown = "unknown" 189 RuleHealthBad = "err" 190 191 // Possible values for MetricType 192 MetricTypeCounter MetricType = "counter" 193 MetricTypeGauge MetricType = "gauge" 194 MetricTypeHistogram MetricType = "histogram" 195 MetricTypeGaugeHistogram MetricType = "gaugehistogram" 196 MetricTypeSummary MetricType = "summary" 197 MetricTypeInfo MetricType = "info" 198 MetricTypeStateset MetricType = "stateset" 199 MetricTypeUnknown MetricType = "unknown" 200) 201 202// Error is an error returned by the API. 203type Error struct { 204 Type ErrorType 205 Msg string 206 Detail string 207} 208 209func (e *Error) Error() string { 210 return fmt.Sprintf("%s: %s", e.Type, e.Msg) 211} 212 213// Range represents a sliced time range. 214type Range struct { 215 // The boundaries of the time range. 216 Start, End time.Time 217 // The maximum time between two slices within the boundaries. 218 Step time.Duration 219} 220 221// API provides bindings for Prometheus's v1 API. 222type API interface { 223 // Alerts returns a list of all active alerts. 224 Alerts(ctx context.Context) (AlertsResult, error) 225 // AlertManagers returns an overview of the current state of the Prometheus alert manager discovery. 226 AlertManagers(ctx context.Context) (AlertManagersResult, error) 227 // CleanTombstones removes the deleted data from disk and cleans up the existing tombstones. 228 CleanTombstones(ctx context.Context) error 229 // Config returns the current Prometheus configuration. 230 Config(ctx context.Context) (ConfigResult, error) 231 // DeleteSeries deletes data for a selection of series in a time range. 232 DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error 233 // Flags returns the flag values that Prometheus was launched with. 234 Flags(ctx context.Context) (FlagsResult, error) 235 // LabelNames returns the unique label names present in the block in sorted order by given time range and matchers. 236 LabelNames(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]string, Warnings, error) 237 // LabelValues performs a query for the values of the given label, time range and matchers. 238 LabelValues(ctx context.Context, label string, matches []string, startTime time.Time, endTime time.Time) (model.LabelValues, Warnings, error) 239 // Query performs a query for the given time. 240 Query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, error) 241 // QueryRange performs a query for the given range. 242 QueryRange(ctx context.Context, query string, r Range) (model.Value, Warnings, error) 243 // QueryExemplars performs a query for exemplars by the given query and time range. 244 QueryExemplars(ctx context.Context, query string, startTime time.Time, endTime time.Time) ([]ExemplarQueryResult, error) 245 // Buildinfo returns various build information properties about the Prometheus server 246 Buildinfo(ctx context.Context) (BuildinfoResult, error) 247 // Runtimeinfo returns the various runtime information properties about the Prometheus server. 248 Runtimeinfo(ctx context.Context) (RuntimeinfoResult, error) 249 // Series finds series by label matchers. 250 Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, Warnings, error) 251 // Snapshot creates a snapshot of all current data into snapshots/<datetime>-<rand> 252 // under the TSDB's data directory and returns the directory as response. 253 Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error) 254 // Rules returns a list of alerting and recording rules that are currently loaded. 255 Rules(ctx context.Context) (RulesResult, error) 256 // Targets returns an overview of the current state of the Prometheus target discovery. 257 Targets(ctx context.Context) (TargetsResult, error) 258 // TargetsMetadata returns metadata about metrics currently scraped by the target. 259 TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]MetricMetadata, error) 260 // Metadata returns metadata about metrics currently scraped by the metric name. 261 Metadata(ctx context.Context, metric string, limit string) (map[string][]Metadata, error) 262 // TSDB returns the cardinality statistics. 263 TSDB(ctx context.Context) (TSDBResult, error) 264} 265 266// AlertsResult contains the result from querying the alerts endpoint. 267type AlertsResult struct { 268 Alerts []Alert `json:"alerts"` 269} 270 271// AlertManagersResult contains the result from querying the alertmanagers endpoint. 272type AlertManagersResult struct { 273 Active []AlertManager `json:"activeAlertManagers"` 274 Dropped []AlertManager `json:"droppedAlertManagers"` 275} 276 277// AlertManager models a configured Alert Manager. 278type AlertManager struct { 279 URL string `json:"url"` 280} 281 282// ConfigResult contains the result from querying the config endpoint. 283type ConfigResult struct { 284 YAML string `json:"yaml"` 285} 286 287// FlagsResult contains the result from querying the flag endpoint. 288type FlagsResult map[string]string 289 290// BuildinfoResult contains the results from querying the buildinfo endpoint. 291type BuildinfoResult struct { 292 Version string `json:"version"` 293 Revision string `json:"revision"` 294 Branch string `json:"branch"` 295 BuildUser string `json:"buildUser"` 296 BuildDate string `json:"buildDate"` 297 GoVersion string `json:"goVersion"` 298} 299 300// RuntimeinfoResult contains the result from querying the runtimeinfo endpoint. 301type RuntimeinfoResult struct { 302 StartTime time.Time `json:"startTime"` 303 CWD string `json:"CWD"` 304 ReloadConfigSuccess bool `json:"reloadConfigSuccess"` 305 LastConfigTime time.Time `json:"lastConfigTime"` 306 ChunkCount int `json:"chunkCount"` 307 TimeSeriesCount int `json:"timeSeriesCount"` 308 CorruptionCount int `json:"corruptionCount"` 309 GoroutineCount int `json:"goroutineCount"` 310 GOMAXPROCS int `json:"GOMAXPROCS"` 311 GOGC string `json:"GOGC"` 312 GODEBUG string `json:"GODEBUG"` 313 StorageRetention string `json:"storageRetention"` 314} 315 316// SnapshotResult contains the result from querying the snapshot endpoint. 317type SnapshotResult struct { 318 Name string `json:"name"` 319} 320 321// RulesResult contains the result from querying the rules endpoint. 322type RulesResult struct { 323 Groups []RuleGroup `json:"groups"` 324} 325 326// RuleGroup models a rule group that contains a set of recording and alerting rules. 327type RuleGroup struct { 328 Name string `json:"name"` 329 File string `json:"file"` 330 Interval float64 `json:"interval"` 331 Rules Rules `json:"rules"` 332} 333 334// Recording and alerting rules are stored in the same slice to preserve the order 335// that rules are returned in by the API. 336// 337// Rule types can be determined using a type switch: 338// switch v := rule.(type) { 339// case RecordingRule: 340// fmt.Print("got a recording rule") 341// case AlertingRule: 342// fmt.Print("got a alerting rule") 343// default: 344// fmt.Printf("unknown rule type %s", v) 345// } 346type Rules []interface{} 347 348// AlertingRule models a alerting rule. 349type AlertingRule struct { 350 Name string `json:"name"` 351 Query string `json:"query"` 352 Duration float64 `json:"duration"` 353 Labels model.LabelSet `json:"labels"` 354 Annotations model.LabelSet `json:"annotations"` 355 Alerts []*Alert `json:"alerts"` 356 Health RuleHealth `json:"health"` 357 LastError string `json:"lastError,omitempty"` 358 EvaluationTime float64 `json:"evaluationTime"` 359 LastEvaluation time.Time `json:"lastEvaluation"` 360 State string `json:"state"` 361} 362 363// RecordingRule models a recording rule. 364type RecordingRule struct { 365 Name string `json:"name"` 366 Query string `json:"query"` 367 Labels model.LabelSet `json:"labels,omitempty"` 368 Health RuleHealth `json:"health"` 369 LastError string `json:"lastError,omitempty"` 370 EvaluationTime float64 `json:"evaluationTime"` 371 LastEvaluation time.Time `json:"lastEvaluation"` 372} 373 374// Alert models an active alert. 375type Alert struct { 376 ActiveAt time.Time `json:"activeAt"` 377 Annotations model.LabelSet 378 Labels model.LabelSet 379 State AlertState 380 Value string 381} 382 383// TargetsResult contains the result from querying the targets endpoint. 384type TargetsResult struct { 385 Active []ActiveTarget `json:"activeTargets"` 386 Dropped []DroppedTarget `json:"droppedTargets"` 387} 388 389// ActiveTarget models an active Prometheus scrape target. 390type ActiveTarget struct { 391 DiscoveredLabels map[string]string `json:"discoveredLabels"` 392 Labels model.LabelSet `json:"labels"` 393 ScrapePool string `json:"scrapePool"` 394 ScrapeURL string `json:"scrapeUrl"` 395 GlobalURL string `json:"globalUrl"` 396 LastError string `json:"lastError"` 397 LastScrape time.Time `json:"lastScrape"` 398 LastScrapeDuration float64 `json:"lastScrapeDuration"` 399 Health HealthStatus `json:"health"` 400} 401 402// DroppedTarget models a dropped Prometheus scrape target. 403type DroppedTarget struct { 404 DiscoveredLabels map[string]string `json:"discoveredLabels"` 405} 406 407// MetricMetadata models the metadata of a metric with its scrape target and name. 408type MetricMetadata struct { 409 Target map[string]string `json:"target"` 410 Metric string `json:"metric,omitempty"` 411 Type MetricType `json:"type"` 412 Help string `json:"help"` 413 Unit string `json:"unit"` 414} 415 416// Metadata models the metadata of a metric. 417type Metadata struct { 418 Type MetricType `json:"type"` 419 Help string `json:"help"` 420 Unit string `json:"unit"` 421} 422 423// queryResult contains result data for a query. 424type queryResult struct { 425 Type model.ValueType `json:"resultType"` 426 Result interface{} `json:"result"` 427 428 // The decoded value. 429 v model.Value 430} 431 432// TSDBResult contains the result from querying the tsdb endpoint. 433type TSDBResult struct { 434 SeriesCountByMetricName []Stat `json:"seriesCountByMetricName"` 435 LabelValueCountByLabelName []Stat `json:"labelValueCountByLabelName"` 436 MemoryInBytesByLabelName []Stat `json:"memoryInBytesByLabelName"` 437 SeriesCountByLabelValuePair []Stat `json:"seriesCountByLabelValuePair"` 438} 439 440// Stat models information about statistic value. 441type Stat struct { 442 Name string `json:"name"` 443 Value uint64 `json:"value"` 444} 445 446func (rg *RuleGroup) UnmarshalJSON(b []byte) error { 447 v := struct { 448 Name string `json:"name"` 449 File string `json:"file"` 450 Interval float64 `json:"interval"` 451 Rules []json.RawMessage `json:"rules"` 452 }{} 453 454 if err := json.Unmarshal(b, &v); err != nil { 455 return err 456 } 457 458 rg.Name = v.Name 459 rg.File = v.File 460 rg.Interval = v.Interval 461 462 for _, rule := range v.Rules { 463 alertingRule := AlertingRule{} 464 if err := json.Unmarshal(rule, &alertingRule); err == nil { 465 rg.Rules = append(rg.Rules, alertingRule) 466 continue 467 } 468 recordingRule := RecordingRule{} 469 if err := json.Unmarshal(rule, &recordingRule); err == nil { 470 rg.Rules = append(rg.Rules, recordingRule) 471 continue 472 } 473 return errors.New("failed to decode JSON into an alerting or recording rule") 474 } 475 476 return nil 477} 478 479func (r *AlertingRule) UnmarshalJSON(b []byte) error { 480 v := struct { 481 Type string `json:"type"` 482 }{} 483 if err := json.Unmarshal(b, &v); err != nil { 484 return err 485 } 486 if v.Type == "" { 487 return errors.New("type field not present in rule") 488 } 489 if v.Type != string(RuleTypeAlerting) { 490 return fmt.Errorf("expected rule of type %s but got %s", string(RuleTypeAlerting), v.Type) 491 } 492 493 rule := struct { 494 Name string `json:"name"` 495 Query string `json:"query"` 496 Duration float64 `json:"duration"` 497 Labels model.LabelSet `json:"labels"` 498 Annotations model.LabelSet `json:"annotations"` 499 Alerts []*Alert `json:"alerts"` 500 Health RuleHealth `json:"health"` 501 LastError string `json:"lastError,omitempty"` 502 EvaluationTime float64 `json:"evaluationTime"` 503 LastEvaluation time.Time `json:"lastEvaluation"` 504 State string `json:"state"` 505 }{} 506 if err := json.Unmarshal(b, &rule); err != nil { 507 return err 508 } 509 r.Health = rule.Health 510 r.Annotations = rule.Annotations 511 r.Name = rule.Name 512 r.Query = rule.Query 513 r.Alerts = rule.Alerts 514 r.Duration = rule.Duration 515 r.Labels = rule.Labels 516 r.LastError = rule.LastError 517 r.EvaluationTime = rule.EvaluationTime 518 r.LastEvaluation = rule.LastEvaluation 519 r.State = rule.State 520 521 return nil 522} 523 524func (r *RecordingRule) UnmarshalJSON(b []byte) error { 525 v := struct { 526 Type string `json:"type"` 527 }{} 528 if err := json.Unmarshal(b, &v); err != nil { 529 return err 530 } 531 if v.Type == "" { 532 return errors.New("type field not present in rule") 533 } 534 if v.Type != string(RuleTypeRecording) { 535 return fmt.Errorf("expected rule of type %s but got %s", string(RuleTypeRecording), v.Type) 536 } 537 538 rule := struct { 539 Name string `json:"name"` 540 Query string `json:"query"` 541 Labels model.LabelSet `json:"labels,omitempty"` 542 Health RuleHealth `json:"health"` 543 LastError string `json:"lastError,omitempty"` 544 EvaluationTime float64 `json:"evaluationTime"` 545 LastEvaluation time.Time `json:"lastEvaluation"` 546 }{} 547 if err := json.Unmarshal(b, &rule); err != nil { 548 return err 549 } 550 r.Health = rule.Health 551 r.Labels = rule.Labels 552 r.Name = rule.Name 553 r.LastError = rule.LastError 554 r.Query = rule.Query 555 r.EvaluationTime = rule.EvaluationTime 556 r.LastEvaluation = rule.LastEvaluation 557 558 return nil 559} 560 561func (qr *queryResult) UnmarshalJSON(b []byte) error { 562 v := struct { 563 Type model.ValueType `json:"resultType"` 564 Result json.RawMessage `json:"result"` 565 }{} 566 567 err := json.Unmarshal(b, &v) 568 if err != nil { 569 return err 570 } 571 572 switch v.Type { 573 case model.ValScalar: 574 var sv model.Scalar 575 err = json.Unmarshal(v.Result, &sv) 576 qr.v = &sv 577 578 case model.ValVector: 579 var vv model.Vector 580 err = json.Unmarshal(v.Result, &vv) 581 qr.v = vv 582 583 case model.ValMatrix: 584 var mv model.Matrix 585 err = json.Unmarshal(v.Result, &mv) 586 qr.v = mv 587 588 default: 589 err = fmt.Errorf("unexpected value type %q", v.Type) 590 } 591 return err 592} 593 594// Exemplar is additional information associated with a time series. 595type Exemplar struct { 596 Labels model.LabelSet `json:"labels"` 597 Value model.SampleValue `json:"value"` 598 Timestamp model.Time `json:"timestamp"` 599} 600 601type ExemplarQueryResult struct { 602 SeriesLabels model.LabelSet `json:"seriesLabels"` 603 Exemplars []Exemplar `json:"exemplars"` 604} 605 606// NewAPI returns a new API for the client. 607// 608// It is safe to use the returned API from multiple goroutines. 609func NewAPI(c api.Client) API { 610 return &httpAPI{ 611 client: &apiClientImpl{ 612 client: c, 613 }, 614 } 615} 616 617type httpAPI struct { 618 client apiClient 619} 620 621func (h *httpAPI) Alerts(ctx context.Context) (AlertsResult, error) { 622 u := h.client.URL(epAlerts, nil) 623 624 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 625 if err != nil { 626 return AlertsResult{}, err 627 } 628 629 _, body, _, err := h.client.Do(ctx, req) 630 if err != nil { 631 return AlertsResult{}, err 632 } 633 634 var res AlertsResult 635 return res, json.Unmarshal(body, &res) 636} 637 638func (h *httpAPI) AlertManagers(ctx context.Context) (AlertManagersResult, error) { 639 u := h.client.URL(epAlertManagers, nil) 640 641 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 642 if err != nil { 643 return AlertManagersResult{}, err 644 } 645 646 _, body, _, err := h.client.Do(ctx, req) 647 if err != nil { 648 return AlertManagersResult{}, err 649 } 650 651 var res AlertManagersResult 652 return res, json.Unmarshal(body, &res) 653} 654 655func (h *httpAPI) CleanTombstones(ctx context.Context) error { 656 u := h.client.URL(epCleanTombstones, nil) 657 658 req, err := http.NewRequest(http.MethodPost, u.String(), nil) 659 if err != nil { 660 return err 661 } 662 663 _, _, _, err = h.client.Do(ctx, req) 664 return err 665} 666 667func (h *httpAPI) Config(ctx context.Context) (ConfigResult, error) { 668 u := h.client.URL(epConfig, nil) 669 670 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 671 if err != nil { 672 return ConfigResult{}, err 673 } 674 675 _, body, _, err := h.client.Do(ctx, req) 676 if err != nil { 677 return ConfigResult{}, err 678 } 679 680 var res ConfigResult 681 return res, json.Unmarshal(body, &res) 682} 683 684func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error { 685 u := h.client.URL(epDeleteSeries, nil) 686 q := u.Query() 687 688 for _, m := range matches { 689 q.Add("match[]", m) 690 } 691 692 q.Set("start", formatTime(startTime)) 693 q.Set("end", formatTime(endTime)) 694 695 u.RawQuery = q.Encode() 696 697 req, err := http.NewRequest(http.MethodPost, u.String(), nil) 698 if err != nil { 699 return err 700 } 701 702 _, _, _, err = h.client.Do(ctx, req) 703 return err 704} 705 706func (h *httpAPI) Flags(ctx context.Context) (FlagsResult, error) { 707 u := h.client.URL(epFlags, nil) 708 709 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 710 if err != nil { 711 return FlagsResult{}, err 712 } 713 714 _, body, _, err := h.client.Do(ctx, req) 715 if err != nil { 716 return FlagsResult{}, err 717 } 718 719 var res FlagsResult 720 return res, json.Unmarshal(body, &res) 721} 722 723func (h *httpAPI) Buildinfo(ctx context.Context) (BuildinfoResult, error) { 724 u := h.client.URL(epBuildinfo, nil) 725 726 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 727 if err != nil { 728 return BuildinfoResult{}, err 729 } 730 731 _, body, _, err := h.client.Do(ctx, req) 732 if err != nil { 733 return BuildinfoResult{}, err 734 } 735 736 var res BuildinfoResult 737 return res, json.Unmarshal(body, &res) 738} 739 740func (h *httpAPI) Runtimeinfo(ctx context.Context) (RuntimeinfoResult, error) { 741 u := h.client.URL(epRuntimeinfo, nil) 742 743 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 744 if err != nil { 745 return RuntimeinfoResult{}, err 746 } 747 748 _, body, _, err := h.client.Do(ctx, req) 749 if err != nil { 750 return RuntimeinfoResult{}, err 751 } 752 753 var res RuntimeinfoResult 754 return res, json.Unmarshal(body, &res) 755} 756 757func (h *httpAPI) LabelNames(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]string, Warnings, error) { 758 u := h.client.URL(epLabels, nil) 759 q := u.Query() 760 q.Set("start", formatTime(startTime)) 761 q.Set("end", formatTime(endTime)) 762 for _, m := range matches { 763 q.Add("match[]", m) 764 } 765 766 u.RawQuery = q.Encode() 767 768 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 769 if err != nil { 770 return nil, nil, err 771 } 772 _, body, w, err := h.client.Do(ctx, req) 773 if err != nil { 774 return nil, w, err 775 } 776 var labelNames []string 777 return labelNames, w, json.Unmarshal(body, &labelNames) 778} 779 780func (h *httpAPI) LabelValues(ctx context.Context, label string, matches []string, startTime time.Time, endTime time.Time) (model.LabelValues, Warnings, error) { 781 u := h.client.URL(epLabelValues, map[string]string{"name": label}) 782 q := u.Query() 783 q.Set("start", formatTime(startTime)) 784 q.Set("end", formatTime(endTime)) 785 for _, m := range matches { 786 q.Add("match[]", m) 787 } 788 789 u.RawQuery = q.Encode() 790 791 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 792 if err != nil { 793 return nil, nil, err 794 } 795 _, body, w, err := h.client.Do(ctx, req) 796 if err != nil { 797 return nil, w, err 798 } 799 var labelValues model.LabelValues 800 return labelValues, w, json.Unmarshal(body, &labelValues) 801} 802 803func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, error) { 804 u := h.client.URL(epQuery, nil) 805 q := u.Query() 806 807 q.Set("query", query) 808 if !ts.IsZero() { 809 q.Set("time", formatTime(ts)) 810 } 811 812 _, body, warnings, err := h.client.DoGetFallback(ctx, u, q) 813 if err != nil { 814 return nil, warnings, err 815 } 816 817 var qres queryResult 818 return model.Value(qres.v), warnings, json.Unmarshal(body, &qres) 819} 820 821func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, Warnings, error) { 822 u := h.client.URL(epQueryRange, nil) 823 q := u.Query() 824 825 q.Set("query", query) 826 q.Set("start", formatTime(r.Start)) 827 q.Set("end", formatTime(r.End)) 828 q.Set("step", strconv.FormatFloat(r.Step.Seconds(), 'f', -1, 64)) 829 830 _, body, warnings, err := h.client.DoGetFallback(ctx, u, q) 831 if err != nil { 832 return nil, warnings, err 833 } 834 835 var qres queryResult 836 837 return model.Value(qres.v), warnings, json.Unmarshal(body, &qres) 838} 839 840func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, Warnings, error) { 841 u := h.client.URL(epSeries, nil) 842 q := u.Query() 843 844 for _, m := range matches { 845 q.Add("match[]", m) 846 } 847 848 q.Set("start", formatTime(startTime)) 849 q.Set("end", formatTime(endTime)) 850 851 u.RawQuery = q.Encode() 852 853 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 854 if err != nil { 855 return nil, nil, err 856 } 857 858 _, body, warnings, err := h.client.Do(ctx, req) 859 if err != nil { 860 return nil, warnings, err 861 } 862 863 var mset []model.LabelSet 864 return mset, warnings, json.Unmarshal(body, &mset) 865} 866 867func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error) { 868 u := h.client.URL(epSnapshot, nil) 869 q := u.Query() 870 871 q.Set("skip_head", strconv.FormatBool(skipHead)) 872 873 u.RawQuery = q.Encode() 874 875 req, err := http.NewRequest(http.MethodPost, u.String(), nil) 876 if err != nil { 877 return SnapshotResult{}, err 878 } 879 880 _, body, _, err := h.client.Do(ctx, req) 881 if err != nil { 882 return SnapshotResult{}, err 883 } 884 885 var res SnapshotResult 886 return res, json.Unmarshal(body, &res) 887} 888 889func (h *httpAPI) Rules(ctx context.Context) (RulesResult, error) { 890 u := h.client.URL(epRules, nil) 891 892 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 893 if err != nil { 894 return RulesResult{}, err 895 } 896 897 _, body, _, err := h.client.Do(ctx, req) 898 if err != nil { 899 return RulesResult{}, err 900 } 901 902 var res RulesResult 903 return res, json.Unmarshal(body, &res) 904} 905 906func (h *httpAPI) Targets(ctx context.Context) (TargetsResult, error) { 907 u := h.client.URL(epTargets, nil) 908 909 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 910 if err != nil { 911 return TargetsResult{}, err 912 } 913 914 _, body, _, err := h.client.Do(ctx, req) 915 if err != nil { 916 return TargetsResult{}, err 917 } 918 919 var res TargetsResult 920 return res, json.Unmarshal(body, &res) 921} 922 923func (h *httpAPI) TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]MetricMetadata, error) { 924 u := h.client.URL(epTargetsMetadata, nil) 925 q := u.Query() 926 927 q.Set("match_target", matchTarget) 928 q.Set("metric", metric) 929 q.Set("limit", limit) 930 931 u.RawQuery = q.Encode() 932 933 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 934 if err != nil { 935 return nil, err 936 } 937 938 _, body, _, err := h.client.Do(ctx, req) 939 if err != nil { 940 return nil, err 941 } 942 943 var res []MetricMetadata 944 return res, json.Unmarshal(body, &res) 945} 946 947func (h *httpAPI) Metadata(ctx context.Context, metric string, limit string) (map[string][]Metadata, error) { 948 u := h.client.URL(epMetadata, nil) 949 q := u.Query() 950 951 q.Set("metric", metric) 952 q.Set("limit", limit) 953 954 u.RawQuery = q.Encode() 955 956 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 957 if err != nil { 958 return nil, err 959 } 960 961 _, body, _, err := h.client.Do(ctx, req) 962 if err != nil { 963 return nil, err 964 } 965 966 var res map[string][]Metadata 967 return res, json.Unmarshal(body, &res) 968} 969 970func (h *httpAPI) TSDB(ctx context.Context) (TSDBResult, error) { 971 u := h.client.URL(epTSDB, nil) 972 973 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 974 if err != nil { 975 return TSDBResult{}, err 976 } 977 978 _, body, _, err := h.client.Do(ctx, req) 979 if err != nil { 980 return TSDBResult{}, err 981 } 982 983 var res TSDBResult 984 return res, json.Unmarshal(body, &res) 985} 986 987func (h *httpAPI) QueryExemplars(ctx context.Context, query string, startTime time.Time, endTime time.Time) ([]ExemplarQueryResult, error) { 988 u := h.client.URL(epQueryExemplars, nil) 989 q := u.Query() 990 991 q.Set("query", query) 992 q.Set("start", formatTime(startTime)) 993 q.Set("end", formatTime(endTime)) 994 u.RawQuery = q.Encode() 995 996 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 997 if err != nil { 998 return nil, err 999 } 1000 1001 _, body, _, err := h.client.Do(ctx, req) 1002 if err != nil { 1003 return nil, err 1004 } 1005 1006 var res []ExemplarQueryResult 1007 return res, json.Unmarshal(body, &res) 1008} 1009 1010// Warnings is an array of non critical errors 1011type Warnings []string 1012 1013// apiClient wraps a regular client and processes successful API responses. 1014// Successful also includes responses that errored at the API level. 1015type apiClient interface { 1016 URL(ep string, args map[string]string) *url.URL 1017 Do(context.Context, *http.Request) (*http.Response, []byte, Warnings, error) 1018 DoGetFallback(ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Warnings, error) 1019} 1020 1021type apiClientImpl struct { 1022 client api.Client 1023} 1024 1025type apiResponse struct { 1026 Status string `json:"status"` 1027 Data json.RawMessage `json:"data"` 1028 ErrorType ErrorType `json:"errorType"` 1029 Error string `json:"error"` 1030 Warnings []string `json:"warnings,omitempty"` 1031} 1032 1033func apiError(code int) bool { 1034 // These are the codes that Prometheus sends when it returns an error. 1035 return code == http.StatusUnprocessableEntity || code == http.StatusBadRequest 1036} 1037 1038func errorTypeAndMsgFor(resp *http.Response) (ErrorType, string) { 1039 switch resp.StatusCode / 100 { 1040 case 4: 1041 return ErrClient, fmt.Sprintf("client error: %d", resp.StatusCode) 1042 case 5: 1043 return ErrServer, fmt.Sprintf("server error: %d", resp.StatusCode) 1044 } 1045 return ErrBadResponse, fmt.Sprintf("bad response code %d", resp.StatusCode) 1046} 1047 1048func (h *apiClientImpl) URL(ep string, args map[string]string) *url.URL { 1049 return h.client.URL(ep, args) 1050} 1051 1052func (h *apiClientImpl) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, Warnings, error) { 1053 resp, body, err := h.client.Do(ctx, req) 1054 if err != nil { 1055 return resp, body, nil, err 1056 } 1057 1058 code := resp.StatusCode 1059 1060 if code/100 != 2 && !apiError(code) { 1061 errorType, errorMsg := errorTypeAndMsgFor(resp) 1062 return resp, body, nil, &Error{ 1063 Type: errorType, 1064 Msg: errorMsg, 1065 Detail: string(body), 1066 } 1067 } 1068 1069 var result apiResponse 1070 1071 if http.StatusNoContent != code { 1072 if jsonErr := json.Unmarshal(body, &result); jsonErr != nil { 1073 return resp, body, nil, &Error{ 1074 Type: ErrBadResponse, 1075 Msg: jsonErr.Error(), 1076 } 1077 } 1078 } 1079 1080 if apiError(code) && result.Status == "success" { 1081 err = &Error{ 1082 Type: ErrBadResponse, 1083 Msg: "inconsistent body for response code", 1084 } 1085 } 1086 1087 if result.Status == "error" { 1088 err = &Error{ 1089 Type: result.ErrorType, 1090 Msg: result.Error, 1091 } 1092 } 1093 1094 return resp, []byte(result.Data), result.Warnings, err 1095 1096} 1097 1098// DoGetFallback will attempt to do the request as-is, and on a 405 or 501 it 1099// will fallback to a GET request. 1100func (h *apiClientImpl) DoGetFallback(ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Warnings, error) { 1101 req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(args.Encode())) 1102 if err != nil { 1103 return nil, nil, nil, err 1104 } 1105 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 1106 1107 resp, body, warnings, err := h.Do(ctx, req) 1108 if resp != nil && (resp.StatusCode == http.StatusMethodNotAllowed || resp.StatusCode == http.StatusNotImplemented) { 1109 u.RawQuery = args.Encode() 1110 req, err = http.NewRequest(http.MethodGet, u.String(), nil) 1111 if err != nil { 1112 return nil, nil, warnings, err 1113 } 1114 1115 } else { 1116 if err != nil { 1117 return resp, body, warnings, err 1118 } 1119 return resp, body, warnings, nil 1120 } 1121 return h.Do(ctx, req) 1122} 1123 1124func formatTime(t time.Time) string { 1125 return strconv.FormatFloat(float64(t.Unix())+float64(t.Nanosecond())/1e9, 'f', -1, 64) 1126} 1127