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