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