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