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