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 statusAPIError = 422 121 122 apiPrefix = "/api/v1" 123 124 epAlerts = apiPrefix + "/alerts" 125 epAlertManagers = apiPrefix + "/alertmanagers" 126 epQuery = apiPrefix + "/query" 127 epQueryRange = apiPrefix + "/query_range" 128 epLabels = apiPrefix + "/labels" 129 epLabelValues = apiPrefix + "/label/:name/values" 130 epSeries = apiPrefix + "/series" 131 epTargets = apiPrefix + "/targets" 132 epTargetsMetadata = apiPrefix + "/targets/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) 140 141// AlertState models the state of an alert. 142type AlertState string 143 144// ErrorType models the different API error types. 145type ErrorType string 146 147// HealthStatus models the health status of a scrape target. 148type HealthStatus string 149 150// RuleType models the type of a rule. 151type RuleType string 152 153// RuleHealth models the health status of a rule. 154type RuleHealth string 155 156// MetricType models the type of a metric. 157type MetricType string 158 159const ( 160 // Possible values for AlertState. 161 AlertStateFiring AlertState = "firing" 162 AlertStateInactive AlertState = "inactive" 163 AlertStatePending AlertState = "pending" 164 165 // Possible values for ErrorType. 166 ErrBadData ErrorType = "bad_data" 167 ErrTimeout ErrorType = "timeout" 168 ErrCanceled ErrorType = "canceled" 169 ErrExec ErrorType = "execution" 170 ErrBadResponse ErrorType = "bad_response" 171 ErrServer ErrorType = "server_error" 172 ErrClient ErrorType = "client_error" 173 174 // Possible values for HealthStatus. 175 HealthGood HealthStatus = "up" 176 HealthUnknown HealthStatus = "unknown" 177 HealthBad HealthStatus = "down" 178 179 // Possible values for RuleType. 180 RuleTypeRecording RuleType = "recording" 181 RuleTypeAlerting RuleType = "alerting" 182 183 // Possible values for RuleHealth. 184 RuleHealthGood = "ok" 185 RuleHealthUnknown = "unknown" 186 RuleHealthBad = "err" 187 188 // Possible values for MetricType 189 MetricTypeCounter MetricType = "counter" 190 MetricTypeGauge MetricType = "gauge" 191 MetricTypeHistogram MetricType = "histogram" 192 MetricTypeGaugeHistogram MetricType = "gaugehistogram" 193 MetricTypeSummary MetricType = "summary" 194 MetricTypeInfo MetricType = "info" 195 MetricTypeStateset MetricType = "stateset" 196 MetricTypeUnknown MetricType = "unknown" 197) 198 199// Error is an error returned by the API. 200type Error struct { 201 Type ErrorType 202 Msg string 203 Detail string 204} 205 206func (e *Error) Error() string { 207 return fmt.Sprintf("%s: %s", e.Type, e.Msg) 208} 209 210// Range represents a sliced time range. 211type Range struct { 212 // The boundaries of the time range. 213 Start, End time.Time 214 // The maximum time between two slices within the boundaries. 215 Step time.Duration 216} 217 218// API provides bindings for Prometheus's v1 API. 219type API interface { 220 // Alerts returns a list of all active alerts. 221 Alerts(ctx context.Context) (AlertsResult, error) 222 // AlertManagers returns an overview of the current state of the Prometheus alert manager discovery. 223 AlertManagers(ctx context.Context) (AlertManagersResult, error) 224 // CleanTombstones removes the deleted data from disk and cleans up the existing tombstones. 225 CleanTombstones(ctx context.Context) error 226 // Config returns the current Prometheus configuration. 227 Config(ctx context.Context) (ConfigResult, error) 228 // DeleteSeries deletes data for a selection of series in a time range. 229 DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error 230 // Flags returns the flag values that Prometheus was launched with. 231 Flags(ctx context.Context) (FlagsResult, error) 232 // LabelNames returns all the unique label names present in the block in sorted order. 233 LabelNames(ctx context.Context) ([]string, Warnings, error) 234 // LabelValues performs a query for the values of the given label. 235 LabelValues(ctx context.Context, label string) (model.LabelValues, Warnings, error) 236 // Query performs a query for the given time. 237 Query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, error) 238 // QueryRange performs a query for the given range. 239 QueryRange(ctx context.Context, query string, r Range) (model.Value, Warnings, error) 240 // Series finds series by label matchers. 241 Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, Warnings, error) 242 // Snapshot creates a snapshot of all current data into snapshots/<datetime>-<rand> 243 // under the TSDB's data directory and returns the directory as response. 244 Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error) 245 // Rules returns a list of alerting and recording rules that are currently loaded. 246 Rules(ctx context.Context) (RulesResult, error) 247 // Targets returns an overview of the current state of the Prometheus target discovery. 248 Targets(ctx context.Context) (TargetsResult, error) 249 // TargetsMetadata returns metadata about metrics currently scraped by the target. 250 TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]MetricMetadata, error) 251} 252 253// AlertsResult contains the result from querying the alerts endpoint. 254type AlertsResult struct { 255 Alerts []Alert `json:"alerts"` 256} 257 258// AlertManagersResult contains the result from querying the alertmanagers endpoint. 259type AlertManagersResult struct { 260 Active []AlertManager `json:"activeAlertManagers"` 261 Dropped []AlertManager `json:"droppedAlertManagers"` 262} 263 264// AlertManager models a configured Alert Manager. 265type AlertManager struct { 266 URL string `json:"url"` 267} 268 269// ConfigResult contains the result from querying the config endpoint. 270type ConfigResult struct { 271 YAML string `json:"yaml"` 272} 273 274// FlagsResult contains the result from querying the flag endpoint. 275type FlagsResult map[string]string 276 277// SnapshotResult contains the result from querying the snapshot endpoint. 278type SnapshotResult struct { 279 Name string `json:"name"` 280} 281 282// RulesResult contains the result from querying the rules endpoint. 283type RulesResult struct { 284 Groups []RuleGroup `json:"groups"` 285} 286 287// RuleGroup models a rule group that contains a set of recording and alerting rules. 288type RuleGroup struct { 289 Name string `json:"name"` 290 File string `json:"file"` 291 Interval float64 `json:"interval"` 292 Rules Rules `json:"rules"` 293} 294 295// Recording and alerting rules are stored in the same slice to preserve the order 296// that rules are returned in by the API. 297// 298// Rule types can be determined using a type switch: 299// switch v := rule.(type) { 300// case RecordingRule: 301// fmt.Print("got a recording rule") 302// case AlertingRule: 303// fmt.Print("got a alerting rule") 304// default: 305// fmt.Printf("unknown rule type %s", v) 306// } 307type Rules []interface{} 308 309// AlertingRule models a alerting rule. 310type AlertingRule struct { 311 Name string `json:"name"` 312 Query string `json:"query"` 313 Duration float64 `json:"duration"` 314 Labels model.LabelSet `json:"labels"` 315 Annotations model.LabelSet `json:"annotations"` 316 Alerts []*Alert `json:"alerts"` 317 Health RuleHealth `json:"health"` 318 LastError string `json:"lastError,omitempty"` 319} 320 321// RecordingRule models a recording rule. 322type RecordingRule struct { 323 Name string `json:"name"` 324 Query string `json:"query"` 325 Labels model.LabelSet `json:"labels,omitempty"` 326 Health RuleHealth `json:"health"` 327 LastError string `json:"lastError,omitempty"` 328} 329 330// Alert models an active alert. 331type Alert struct { 332 ActiveAt time.Time `json:"activeAt"` 333 Annotations model.LabelSet 334 Labels model.LabelSet 335 State AlertState 336 Value string 337} 338 339// TargetsResult contains the result from querying the targets endpoint. 340type TargetsResult struct { 341 Active []ActiveTarget `json:"activeTargets"` 342 Dropped []DroppedTarget `json:"droppedTargets"` 343} 344 345// ActiveTarget models an active Prometheus scrape target. 346type ActiveTarget struct { 347 DiscoveredLabels map[string]string `json:"discoveredLabels"` 348 Labels model.LabelSet `json:"labels"` 349 ScrapeURL string `json:"scrapeUrl"` 350 LastError string `json:"lastError"` 351 LastScrape time.Time `json:"lastScrape"` 352 Health HealthStatus `json:"health"` 353} 354 355// DroppedTarget models a dropped Prometheus scrape target. 356type DroppedTarget struct { 357 DiscoveredLabels map[string]string `json:"discoveredLabels"` 358} 359 360// MetricMetadata models the metadata of a metric. 361type MetricMetadata struct { 362 Target map[string]string `json:"target"` 363 Metric string `json:"metric,omitempty"` 364 Type MetricType `json:"type"` 365 Help string `json:"help"` 366 Unit string `json:"unit"` 367} 368 369// queryResult contains result data for a query. 370type queryResult struct { 371 Type model.ValueType `json:"resultType"` 372 Result interface{} `json:"result"` 373 374 // The decoded value. 375 v model.Value 376} 377 378func (rg *RuleGroup) UnmarshalJSON(b []byte) error { 379 v := struct { 380 Name string `json:"name"` 381 File string `json:"file"` 382 Interval float64 `json:"interval"` 383 Rules []json.RawMessage `json:"rules"` 384 }{} 385 386 if err := json.Unmarshal(b, &v); err != nil { 387 return err 388 } 389 390 rg.Name = v.Name 391 rg.File = v.File 392 rg.Interval = v.Interval 393 394 for _, rule := range v.Rules { 395 alertingRule := AlertingRule{} 396 if err := json.Unmarshal(rule, &alertingRule); err == nil { 397 rg.Rules = append(rg.Rules, alertingRule) 398 continue 399 } 400 recordingRule := RecordingRule{} 401 if err := json.Unmarshal(rule, &recordingRule); err == nil { 402 rg.Rules = append(rg.Rules, recordingRule) 403 continue 404 } 405 return errors.New("failed to decode JSON into an alerting or recording rule") 406 } 407 408 return nil 409} 410 411func (r *AlertingRule) UnmarshalJSON(b []byte) error { 412 v := struct { 413 Type string `json:"type"` 414 }{} 415 if err := json.Unmarshal(b, &v); err != nil { 416 return err 417 } 418 if v.Type == "" { 419 return errors.New("type field not present in rule") 420 } 421 if v.Type != string(RuleTypeAlerting) { 422 return fmt.Errorf("expected rule of type %s but got %s", string(RuleTypeAlerting), v.Type) 423 } 424 425 rule := struct { 426 Name string `json:"name"` 427 Query string `json:"query"` 428 Duration float64 `json:"duration"` 429 Labels model.LabelSet `json:"labels"` 430 Annotations model.LabelSet `json:"annotations"` 431 Alerts []*Alert `json:"alerts"` 432 Health RuleHealth `json:"health"` 433 LastError string `json:"lastError,omitempty"` 434 }{} 435 if err := json.Unmarshal(b, &rule); err != nil { 436 return err 437 } 438 r.Health = rule.Health 439 r.Annotations = rule.Annotations 440 r.Name = rule.Name 441 r.Query = rule.Query 442 r.Alerts = rule.Alerts 443 r.Duration = rule.Duration 444 r.Labels = rule.Labels 445 r.LastError = rule.LastError 446 447 return nil 448} 449 450func (r *RecordingRule) UnmarshalJSON(b []byte) error { 451 v := struct { 452 Type string `json:"type"` 453 }{} 454 if err := json.Unmarshal(b, &v); err != nil { 455 return err 456 } 457 if v.Type == "" { 458 return errors.New("type field not present in rule") 459 } 460 if v.Type != string(RuleTypeRecording) { 461 return fmt.Errorf("expected rule of type %s but got %s", string(RuleTypeRecording), v.Type) 462 } 463 464 rule := struct { 465 Name string `json:"name"` 466 Query string `json:"query"` 467 Labels model.LabelSet `json:"labels,omitempty"` 468 Health RuleHealth `json:"health"` 469 LastError string `json:"lastError,omitempty"` 470 }{} 471 if err := json.Unmarshal(b, &rule); err != nil { 472 return err 473 } 474 r.Health = rule.Health 475 r.Labels = rule.Labels 476 r.Name = rule.Name 477 r.LastError = rule.LastError 478 r.Query = rule.Query 479 480 return nil 481} 482 483func (qr *queryResult) UnmarshalJSON(b []byte) error { 484 v := struct { 485 Type model.ValueType `json:"resultType"` 486 Result json.RawMessage `json:"result"` 487 }{} 488 489 err := json.Unmarshal(b, &v) 490 if err != nil { 491 return err 492 } 493 494 switch v.Type { 495 case model.ValScalar: 496 var sv model.Scalar 497 err = json.Unmarshal(v.Result, &sv) 498 qr.v = &sv 499 500 case model.ValVector: 501 var vv model.Vector 502 err = json.Unmarshal(v.Result, &vv) 503 qr.v = vv 504 505 case model.ValMatrix: 506 var mv model.Matrix 507 err = json.Unmarshal(v.Result, &mv) 508 qr.v = mv 509 510 default: 511 err = fmt.Errorf("unexpected value type %q", v.Type) 512 } 513 return err 514} 515 516// NewAPI returns a new API for the client. 517// 518// It is safe to use the returned API from multiple goroutines. 519func NewAPI(c api.Client) API { 520 return &httpAPI{ 521 client: &apiClientImpl{ 522 client: c, 523 }, 524 } 525} 526 527type httpAPI struct { 528 client apiClient 529} 530 531func (h *httpAPI) Alerts(ctx context.Context) (AlertsResult, error) { 532 u := h.client.URL(epAlerts, nil) 533 534 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 535 if err != nil { 536 return AlertsResult{}, err 537 } 538 539 _, body, _, err := h.client.Do(ctx, req) 540 if err != nil { 541 return AlertsResult{}, err 542 } 543 544 var res AlertsResult 545 return res, json.Unmarshal(body, &res) 546} 547 548func (h *httpAPI) AlertManagers(ctx context.Context) (AlertManagersResult, error) { 549 u := h.client.URL(epAlertManagers, nil) 550 551 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 552 if err != nil { 553 return AlertManagersResult{}, err 554 } 555 556 _, body, _, err := h.client.Do(ctx, req) 557 if err != nil { 558 return AlertManagersResult{}, err 559 } 560 561 var res AlertManagersResult 562 return res, json.Unmarshal(body, &res) 563} 564 565func (h *httpAPI) CleanTombstones(ctx context.Context) error { 566 u := h.client.URL(epCleanTombstones, nil) 567 568 req, err := http.NewRequest(http.MethodPost, u.String(), nil) 569 if err != nil { 570 return err 571 } 572 573 _, _, _, err = h.client.Do(ctx, req) 574 return err 575} 576 577func (h *httpAPI) Config(ctx context.Context) (ConfigResult, error) { 578 u := h.client.URL(epConfig, nil) 579 580 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 581 if err != nil { 582 return ConfigResult{}, err 583 } 584 585 _, body, _, err := h.client.Do(ctx, req) 586 if err != nil { 587 return ConfigResult{}, err 588 } 589 590 var res ConfigResult 591 return res, json.Unmarshal(body, &res) 592} 593 594func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error { 595 u := h.client.URL(epDeleteSeries, nil) 596 q := u.Query() 597 598 for _, m := range matches { 599 q.Add("match[]", m) 600 } 601 602 q.Set("start", formatTime(startTime)) 603 q.Set("end", formatTime(endTime)) 604 605 u.RawQuery = q.Encode() 606 607 req, err := http.NewRequest(http.MethodPost, u.String(), nil) 608 if err != nil { 609 return err 610 } 611 612 _, _, _, err = h.client.Do(ctx, req) 613 return err 614} 615 616func (h *httpAPI) Flags(ctx context.Context) (FlagsResult, error) { 617 u := h.client.URL(epFlags, nil) 618 619 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 620 if err != nil { 621 return FlagsResult{}, err 622 } 623 624 _, body, _, err := h.client.Do(ctx, req) 625 if err != nil { 626 return FlagsResult{}, err 627 } 628 629 var res FlagsResult 630 return res, json.Unmarshal(body, &res) 631} 632 633func (h *httpAPI) LabelNames(ctx context.Context) ([]string, Warnings, error) { 634 u := h.client.URL(epLabels, nil) 635 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 636 if err != nil { 637 return nil, nil, err 638 } 639 _, body, w, err := h.client.Do(ctx, req) 640 if err != nil { 641 return nil, w, err 642 } 643 var labelNames []string 644 return labelNames, w, json.Unmarshal(body, &labelNames) 645} 646 647func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, Warnings, error) { 648 u := h.client.URL(epLabelValues, map[string]string{"name": label}) 649 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 650 if err != nil { 651 return nil, nil, err 652 } 653 _, body, w, err := h.client.Do(ctx, req) 654 if err != nil { 655 return nil, w, err 656 } 657 var labelValues model.LabelValues 658 return labelValues, w, json.Unmarshal(body, &labelValues) 659} 660 661func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, error) { 662 u := h.client.URL(epQuery, nil) 663 q := u.Query() 664 665 q.Set("query", query) 666 if !ts.IsZero() { 667 q.Set("time", formatTime(ts)) 668 } 669 670 _, body, warnings, err := h.client.DoGetFallback(ctx, u, q) 671 if err != nil { 672 return nil, warnings, err 673 } 674 675 var qres queryResult 676 return model.Value(qres.v), warnings, json.Unmarshal(body, &qres) 677} 678 679func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, Warnings, error) { 680 u := h.client.URL(epQueryRange, nil) 681 q := u.Query() 682 683 q.Set("query", query) 684 q.Set("start", formatTime(r.Start)) 685 q.Set("end", formatTime(r.End)) 686 q.Set("step", strconv.FormatFloat(r.Step.Seconds(), 'f', -1, 64)) 687 688 _, body, warnings, err := h.client.DoGetFallback(ctx, u, q) 689 if err != nil { 690 return nil, warnings, err 691 } 692 693 var qres queryResult 694 695 return model.Value(qres.v), warnings, json.Unmarshal(body, &qres) 696} 697 698func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, Warnings, error) { 699 u := h.client.URL(epSeries, nil) 700 q := u.Query() 701 702 for _, m := range matches { 703 q.Add("match[]", m) 704 } 705 706 q.Set("start", formatTime(startTime)) 707 q.Set("end", formatTime(endTime)) 708 709 u.RawQuery = q.Encode() 710 711 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 712 if err != nil { 713 return nil, nil, err 714 } 715 716 _, body, warnings, err := h.client.Do(ctx, req) 717 if err != nil { 718 return nil, warnings, err 719 } 720 721 var mset []model.LabelSet 722 return mset, warnings, json.Unmarshal(body, &mset) 723} 724 725func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error) { 726 u := h.client.URL(epSnapshot, nil) 727 q := u.Query() 728 729 q.Set("skip_head", strconv.FormatBool(skipHead)) 730 731 u.RawQuery = q.Encode() 732 733 req, err := http.NewRequest(http.MethodPost, u.String(), nil) 734 if err != nil { 735 return SnapshotResult{}, err 736 } 737 738 _, body, _, err := h.client.Do(ctx, req) 739 if err != nil { 740 return SnapshotResult{}, err 741 } 742 743 var res SnapshotResult 744 return res, json.Unmarshal(body, &res) 745} 746 747func (h *httpAPI) Rules(ctx context.Context) (RulesResult, error) { 748 u := h.client.URL(epRules, nil) 749 750 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 751 if err != nil { 752 return RulesResult{}, err 753 } 754 755 _, body, _, err := h.client.Do(ctx, req) 756 if err != nil { 757 return RulesResult{}, err 758 } 759 760 var res RulesResult 761 return res, json.Unmarshal(body, &res) 762} 763 764func (h *httpAPI) Targets(ctx context.Context) (TargetsResult, error) { 765 u := h.client.URL(epTargets, nil) 766 767 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 768 if err != nil { 769 return TargetsResult{}, err 770 } 771 772 _, body, _, err := h.client.Do(ctx, req) 773 if err != nil { 774 return TargetsResult{}, err 775 } 776 777 var res TargetsResult 778 return res, json.Unmarshal(body, &res) 779} 780 781func (h *httpAPI) TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]MetricMetadata, error) { 782 u := h.client.URL(epTargetsMetadata, nil) 783 q := u.Query() 784 785 q.Set("match_target", matchTarget) 786 q.Set("metric", metric) 787 q.Set("limit", limit) 788 789 u.RawQuery = q.Encode() 790 791 req, err := http.NewRequest(http.MethodGet, u.String(), nil) 792 if err != nil { 793 return nil, err 794 } 795 796 _, body, _, err := h.client.Do(ctx, req) 797 if err != nil { 798 return nil, err 799 } 800 801 var res []MetricMetadata 802 return res, json.Unmarshal(body, &res) 803} 804 805// Warnings is an array of non critical errors 806type Warnings []string 807 808// apiClient wraps a regular client and processes successful API responses. 809// Successful also includes responses that errored at the API level. 810type apiClient interface { 811 URL(ep string, args map[string]string) *url.URL 812 Do(context.Context, *http.Request) (*http.Response, []byte, Warnings, error) 813 DoGetFallback(ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Warnings, error) 814} 815 816type apiClientImpl struct { 817 client api.Client 818} 819 820type apiResponse struct { 821 Status string `json:"status"` 822 Data json.RawMessage `json:"data"` 823 ErrorType ErrorType `json:"errorType"` 824 Error string `json:"error"` 825 Warnings []string `json:"warnings,omitempty"` 826} 827 828func apiError(code int) bool { 829 // These are the codes that Prometheus sends when it returns an error. 830 return code == statusAPIError || code == http.StatusBadRequest 831} 832 833func errorTypeAndMsgFor(resp *http.Response) (ErrorType, string) { 834 switch resp.StatusCode / 100 { 835 case 4: 836 return ErrClient, fmt.Sprintf("client error: %d", resp.StatusCode) 837 case 5: 838 return ErrServer, fmt.Sprintf("server error: %d", resp.StatusCode) 839 } 840 return ErrBadResponse, fmt.Sprintf("bad response code %d", resp.StatusCode) 841} 842 843func (h *apiClientImpl) URL(ep string, args map[string]string) *url.URL { 844 return h.client.URL(ep, args) 845} 846 847func (h *apiClientImpl) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, Warnings, error) { 848 resp, body, err := h.client.Do(ctx, req) 849 if err != nil { 850 return resp, body, nil, err 851 } 852 853 code := resp.StatusCode 854 855 if code/100 != 2 && !apiError(code) { 856 errorType, errorMsg := errorTypeAndMsgFor(resp) 857 return resp, body, nil, &Error{ 858 Type: errorType, 859 Msg: errorMsg, 860 Detail: string(body), 861 } 862 } 863 864 var result apiResponse 865 866 if http.StatusNoContent != code { 867 if jsonErr := json.Unmarshal(body, &result); jsonErr != nil { 868 return resp, body, nil, &Error{ 869 Type: ErrBadResponse, 870 Msg: jsonErr.Error(), 871 } 872 } 873 } 874 875 if apiError(code) != (result.Status == "error") { 876 err = &Error{ 877 Type: ErrBadResponse, 878 Msg: "inconsistent body for response code", 879 } 880 } 881 882 if apiError(code) && result.Status == "error" { 883 err = &Error{ 884 Type: result.ErrorType, 885 Msg: result.Error, 886 } 887 } 888 889 return resp, []byte(result.Data), result.Warnings, err 890 891} 892 893// DoGetFallback will attempt to do the request as-is, and on a 405 it will fallback to a GET request. 894func (h *apiClientImpl) DoGetFallback(ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Warnings, error) { 895 req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(args.Encode())) 896 if err != nil { 897 return nil, nil, nil, err 898 } 899 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 900 901 resp, body, warnings, err := h.Do(ctx, req) 902 if resp != nil && resp.StatusCode == http.StatusMethodNotAllowed { 903 u.RawQuery = args.Encode() 904 req, err = http.NewRequest(http.MethodGet, u.String(), nil) 905 if err != nil { 906 return nil, nil, warnings, err 907 } 908 909 } else { 910 if err != nil { 911 return resp, body, warnings, err 912 } 913 return resp, body, warnings, nil 914 } 915 return h.Do(ctx, req) 916} 917 918func formatTime(t time.Time) string { 919 return strconv.FormatFloat(float64(t.Unix())+float64(t.Nanosecond())/1e9, 'f', -1, 64) 920} 921