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