1
// Copyright 2015 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
6
// http://www.apache.org/licenses/LICENSE-2.0
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.
14
// Package prometheus provides bindings to the Prometheus HTTP API:
15
// http://prometheus.io/docs/querying/api/
30
"github.com/prometheus/common/model"
31
"golang.org/x/net/context"
32
"golang.org/x/net/context/ctxhttp"
40
epQueryRange = "/query_range"
41
epLabelValues = "/label/:name/values"
48
// The different API error types.
49
ErrBadData ErrorType = "bad_data"
50
ErrTimeout = "timeout"
51
ErrCanceled = "canceled"
53
ErrBadResponse = "bad_response"
56
// Error is an error returned by the API.
62
func (e *Error) Error() string {
63
return fmt.Sprintf("%s: %s", e.Type, e.Msg)
66
// CancelableTransport is like net.Transport but provides
67
// per-request cancelation functionality.
68
type CancelableTransport interface {
70
CancelRequest(req *http.Request)
73
var DefaultTransport CancelableTransport = &http.Transport{
74
Proxy: http.ProxyFromEnvironment,
76
Timeout: 30 * time.Second,
77
KeepAlive: 30 * time.Second,
79
TLSHandshakeTimeout: 10 * time.Second,
82
// Config defines configuration parameters for a new client.
84
// The address of the Prometheus to connect to.
87
// Transport is used by the Client to drive HTTP requests. If not
88
// provided, DefaultTransport will be used.
89
Transport CancelableTransport
92
func (cfg *Config) transport() CancelableTransport {
93
if cfg.Transport == nil {
94
return DefaultTransport
99
type Client interface {
100
url(ep string, args map[string]string) *url.URL
101
do(context.Context, *http.Request) (*http.Response, []byte, error)
104
// New returns a new Client.
106
// It is safe to use the returned Client from multiple goroutines.
107
func New(cfg Config) (Client, error) {
108
u, err := url.Parse(cfg.Address)
112
u.Path = strings.TrimRight(u.Path, "/") + apiPrefix
116
transport: cfg.transport(),
120
type httpClient struct {
122
transport CancelableTransport
125
func (c *httpClient) url(ep string, args map[string]string) *url.URL {
126
p := path.Join(c.endpoint.Path, ep)
128
for arg, val := range args {
130
p = strings.Replace(p, arg, val, -1)
139
func (c *httpClient) do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
140
resp, err := ctxhttp.Do(ctx, &http.Client{Transport: c.transport}, req)
153
done := make(chan struct{})
155
body, err = ioutil.ReadAll(resp.Body)
161
err = resp.Body.Close()
169
return resp, body, err
172
// apiClient wraps a regular client and processes successful API responses.
173
// Successful also includes responses that errored at the API level.
174
type apiClient struct {
178
type apiResponse struct {
179
Status string `json:"status"`
180
Data json.RawMessage `json:"data"`
181
ErrorType ErrorType `json:"errorType"`
182
Error string `json:"error"`
185
func (c apiClient) do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
186
resp, body, err := c.Client.do(ctx, req)
188
return resp, body, err
191
code := resp.StatusCode
193
if code/100 != 2 && code != statusAPIError {
194
return resp, body, &Error{
195
Type: ErrBadResponse,
196
Msg: fmt.Sprintf("bad response code %d", resp.StatusCode),
200
var result apiResponse
202
if err = json.Unmarshal(body, &result); err != nil {
203
return resp, body, &Error{
204
Type: ErrBadResponse,
209
if (code == statusAPIError) != (result.Status == "error") {
211
Type: ErrBadResponse,
212
Msg: "inconsistent body for response code",
216
if code == statusAPIError && result.Status == "error" {
218
Type: result.ErrorType,
223
return resp, []byte(result.Data), err
226
// Range represents a sliced time range.
228
// The boundaries of the time range.
230
// The maximum time between two slices within the boundaries.
234
// queryResult contains result data for a query.
235
type queryResult struct {
236
Type model.ValueType `json:"resultType"`
237
Result interface{} `json:"result"`
239
// The decoded value.
243
func (qr *queryResult) UnmarshalJSON(b []byte) error {
245
Type model.ValueType `json:"resultType"`
246
Result json.RawMessage `json:"result"`
249
err := json.Unmarshal(b, &v)
255
case model.ValScalar:
257
err = json.Unmarshal(v.Result, &sv)
260
case model.ValVector:
262
err = json.Unmarshal(v.Result, &vv)
265
case model.ValMatrix:
267
err = json.Unmarshal(v.Result, &mv)
271
err = fmt.Errorf("unexpected value type %q", v.Type)
276
// QueryAPI provides bindings the Prometheus's query API.
277
type QueryAPI interface {
278
// Query performs a query for the given time.
279
Query(ctx context.Context, query string, ts time.Time) (model.Value, error)
280
// Query performs a query for the given range.
281
QueryRange(ctx context.Context, query string, r Range) (model.Value, error)
284
// NewQueryAPI returns a new QueryAPI for the client.
286
// It is safe to use the returned QueryAPI from multiple goroutines.
287
func NewQueryAPI(c Client) QueryAPI {
288
return &httpQueryAPI{client: apiClient{c}}
291
type httpQueryAPI struct {
295
func (h *httpQueryAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, error) {
296
u := h.client.url(epQuery, nil)
299
q.Set("query", query)
300
q.Set("time", ts.Format(time.RFC3339Nano))
302
u.RawQuery = q.Encode()
304
req, _ := http.NewRequest("GET", u.String(), nil)
306
_, body, err := h.client.do(ctx, req)
312
err = json.Unmarshal(body, &qres)
314
return model.Value(qres.v), err
317
func (h *httpQueryAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, error) {
318
u := h.client.url(epQueryRange, nil)
322
start = r.Start.Format(time.RFC3339Nano)
323
end = r.End.Format(time.RFC3339Nano)
324
step = strconv.FormatFloat(r.Step.Seconds(), 'f', 3, 64)
327
q.Set("query", query)
328
q.Set("start", start)
332
u.RawQuery = q.Encode()
334
req, _ := http.NewRequest("GET", u.String(), nil)
336
_, body, err := h.client.do(ctx, req)
342
err = json.Unmarshal(body, &qres)
344
return model.Value(qres.v), err