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