From 2f33fabffeb23450654ebae9478991f6f76db2c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carvalho?= Date: Fri, 31 Mar 2017 19:42:19 -0300 Subject: [PATCH 01/11] api: adds label values query implementation This commit adds a client side implementation for fetching label values from prometheus api. --- api/prometheus/api.go | 16 +++++++++++++++- api/prometheus/api_test.go | 29 ++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/api/prometheus/api.go b/api/prometheus/api.go index cc5cbc3..d2b30b6 100644 --- a/api/prometheus/api.go +++ b/api/prometheus/api.go @@ -280,8 +280,10 @@ func (qr *queryResult) UnmarshalJSON(b []byte) error { type QueryAPI interface { // Query performs a query for the given time. Query(ctx context.Context, query string, ts time.Time) (model.Value, error) - // Query performs a query for the given range. + // QueryRange performs a query for the given range. QueryRange(ctx context.Context, query string, r Range) (model.Value, error) + // QueryLabelValues performs a query for the values of the given label. + QueryLabelValues(ctx context.Context, label string) ([]string, error) } // NewQueryAPI returns a new QueryAPI for the client. @@ -346,3 +348,15 @@ func (h *httpQueryAPI) QueryRange(ctx context.Context, query string, r Range) (m return model.Value(qres.v), err } + +func (h *httpQueryAPI) QueryLabelValues(ctx context.Context, label string) ([]string, error) { + u := h.client.url(epLabelValues, map[string]string{"name": label}) + req, _ := http.NewRequest(http.MethodGet, u.String(), nil) + _, body, err := h.client.do(ctx, req) + if err != nil { + return nil, err + } + var values []string + err = json.Unmarshal(body, &values) + return values, err +} diff --git a/api/prometheus/api_test.go b/api/prometheus/api_test.go index ca084a0..4a38572 100644 --- a/api/prometheus/api_test.go +++ b/api/prometheus/api_test.go @@ -19,6 +19,7 @@ import ( "net/http" "net/url" "reflect" + "strings" "testing" "time" @@ -313,9 +314,13 @@ type apiTest struct { } func (c *apiTestClient) url(ep string, args map[string]string) *url.URL { + path := apiPrefix + ep + for k, v := range args { + path = strings.Replace(path, ":"+k, v, -1) + } u := &url.URL{ Host: "test:9090", - Path: apiPrefix + ep, + Path: path, } return u } @@ -368,6 +373,12 @@ func TestAPIs(t *testing.T) { } } + doQueryLabelValues := func(label string) func() (interface{}, error) { + return func() (interface{}, error) { + return queryAPI.QueryLabelValues(context.Background(), label) + } + } + queryTests := []apiTest{ { do: doQuery("2", testTime), @@ -421,6 +432,22 @@ func TestAPIs(t *testing.T) { }, err: fmt.Errorf("some error"), }, + + { + do: doQueryLabelValues("mylabel"), + inRes: []string{"val1", "val2"}, + reqMethod: "GET", + reqPath: "/api/v1/label/mylabel/values", + res: []string{"val1", "val2"}, + }, + + { + do: doQueryLabelValues("mylabel"), + inErr: fmt.Errorf("some error"), + reqMethod: "GET", + reqPath: "/api/v1/label/mylabel/values", + err: fmt.Errorf("some error"), + }, } var tests []apiTest From 8cb7769e36f9763547a38da7dcf08ddfebe78801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carvalho?= Date: Fri, 31 Mar 2017 21:05:08 -0300 Subject: [PATCH 02/11] api: stops ignoring errors creating requests --- api/prometheus/api.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/api/prometheus/api.go b/api/prometheus/api.go index d2b30b6..29eed5d 100644 --- a/api/prometheus/api.go +++ b/api/prometheus/api.go @@ -306,7 +306,10 @@ func (h *httpQueryAPI) Query(ctx context.Context, query string, ts time.Time) (m u.RawQuery = q.Encode() - req, _ := http.NewRequest("GET", u.String(), nil) + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, err + } _, body, err := h.client.do(ctx, req) if err != nil { @@ -336,7 +339,10 @@ func (h *httpQueryAPI) QueryRange(ctx context.Context, query string, r Range) (m u.RawQuery = q.Encode() - req, _ := http.NewRequest("GET", u.String(), nil) + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, err + } _, body, err := h.client.do(ctx, req) if err != nil { @@ -351,7 +357,10 @@ func (h *httpQueryAPI) QueryRange(ctx context.Context, query string, r Range) (m func (h *httpQueryAPI) QueryLabelValues(ctx context.Context, label string) ([]string, error) { u := h.client.url(epLabelValues, map[string]string{"name": label}) - req, _ := http.NewRequest(http.MethodGet, u.String(), nil) + req, err := http.NewRequest(http.MethodGet, u.String(), nil) + if err != nil { + return nil, err + } _, body, err := h.client.do(ctx, req) if err != nil { return nil, err From 123637b5cabaf9ec32397d742d84ca34a4c17eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carvalho?= Date: Fri, 31 Mar 2017 21:07:16 -0300 Subject: [PATCH 03/11] api: renames method for consistency --- api/prometheus/api.go | 4 ++-- api/prometheus/api_test.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/prometheus/api.go b/api/prometheus/api.go index 29eed5d..9a5cc88 100644 --- a/api/prometheus/api.go +++ b/api/prometheus/api.go @@ -283,7 +283,7 @@ type QueryAPI interface { // QueryRange performs a query for the given range. QueryRange(ctx context.Context, query string, r Range) (model.Value, error) // QueryLabelValues performs a query for the values of the given label. - QueryLabelValues(ctx context.Context, label string) ([]string, error) + LabelValues(ctx context.Context, label string) ([]string, error) } // NewQueryAPI returns a new QueryAPI for the client. @@ -355,7 +355,7 @@ func (h *httpQueryAPI) QueryRange(ctx context.Context, query string, r Range) (m return model.Value(qres.v), err } -func (h *httpQueryAPI) QueryLabelValues(ctx context.Context, label string) ([]string, error) { +func (h *httpQueryAPI) LabelValues(ctx context.Context, label string) ([]string, error) { u := h.client.url(epLabelValues, map[string]string{"name": label}) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { diff --git a/api/prometheus/api_test.go b/api/prometheus/api_test.go index 4a38572..2d6c40b 100644 --- a/api/prometheus/api_test.go +++ b/api/prometheus/api_test.go @@ -373,9 +373,9 @@ func TestAPIs(t *testing.T) { } } - doQueryLabelValues := func(label string) func() (interface{}, error) { + doLabelValues := func(label string) func() (interface{}, error) { return func() (interface{}, error) { - return queryAPI.QueryLabelValues(context.Background(), label) + return queryAPI.LabelValues(context.Background(), label) } } @@ -434,7 +434,7 @@ func TestAPIs(t *testing.T) { }, { - do: doQueryLabelValues("mylabel"), + do: doLabelValues("mylabel"), inRes: []string{"val1", "val2"}, reqMethod: "GET", reqPath: "/api/v1/label/mylabel/values", @@ -442,7 +442,7 @@ func TestAPIs(t *testing.T) { }, { - do: doQueryLabelValues("mylabel"), + do: doLabelValues("mylabel"), inErr: fmt.Errorf("some error"), reqMethod: "GET", reqPath: "/api/v1/label/mylabel/values", From 57f23d303f35768aa6a4de2797991d18d679644a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carvalho?= Date: Fri, 31 Mar 2017 21:29:30 -0300 Subject: [PATCH 04/11] api: returns specific type for label values --- api/prometheus/api.go | 10 +++++----- api/prometheus/api_test.go | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/prometheus/api.go b/api/prometheus/api.go index 9a5cc88..01d857d 100644 --- a/api/prometheus/api.go +++ b/api/prometheus/api.go @@ -283,7 +283,7 @@ type QueryAPI interface { // QueryRange performs a query for the given range. QueryRange(ctx context.Context, query string, r Range) (model.Value, error) // QueryLabelValues performs a query for the values of the given label. - LabelValues(ctx context.Context, label string) ([]string, error) + LabelValues(ctx context.Context, label string) (model.LabelValues, error) } // NewQueryAPI returns a new QueryAPI for the client. @@ -355,7 +355,7 @@ func (h *httpQueryAPI) QueryRange(ctx context.Context, query string, r Range) (m return model.Value(qres.v), err } -func (h *httpQueryAPI) LabelValues(ctx context.Context, label string) ([]string, error) { +func (h *httpQueryAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, error) { u := h.client.url(epLabelValues, map[string]string{"name": label}) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { @@ -365,7 +365,7 @@ func (h *httpQueryAPI) LabelValues(ctx context.Context, label string) ([]string, if err != nil { return nil, err } - var values []string - err = json.Unmarshal(body, &values) - return values, err + var labelValues model.LabelValues + err = json.Unmarshal(body, &labelValues) + return labelValues, err } diff --git a/api/prometheus/api_test.go b/api/prometheus/api_test.go index 2d6c40b..f7ffc7c 100644 --- a/api/prometheus/api_test.go +++ b/api/prometheus/api_test.go @@ -438,7 +438,7 @@ func TestAPIs(t *testing.T) { inRes: []string{"val1", "val2"}, reqMethod: "GET", reqPath: "/api/v1/label/mylabel/values", - res: []string{"val1", "val2"}, + res: model.LabelValues{"val1", "val2"}, }, { From 7b5f0fdaa9cddc9a052dc36224a8773685923072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carvalho?= Date: Fri, 31 Mar 2017 21:31:42 -0300 Subject: [PATCH 05/11] api: fix godoc comment --- api/prometheus/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/prometheus/api.go b/api/prometheus/api.go index 01d857d..61c47ad 100644 --- a/api/prometheus/api.go +++ b/api/prometheus/api.go @@ -282,7 +282,7 @@ type QueryAPI interface { Query(ctx context.Context, query string, ts time.Time) (model.Value, error) // QueryRange performs a query for the given range. QueryRange(ctx context.Context, query string, r Range) (model.Value, error) - // QueryLabelValues performs a query for the values of the given label. + // LabelValues performs a query for the values of the given label. LabelValues(ctx context.Context, label string) (model.LabelValues, error) } From 349922b38c285a2188378904717dc1201dd221a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carvalho?= Date: Wed, 19 Apr 2017 18:24:14 -0300 Subject: [PATCH 06/11] api: creates versioned package for prometheus v1 api This commit creates a new package to hold the prometheus v1 API interface. This interface will contain all the funcionality exposed by Prometheus v1 HTTP API. The underlying http client is kept on the api package since it may be reused across diferent API versions and also by the Alertmanager api package (to come.) --- api/client.go | 136 +++++++++ api/client_test.go | 112 ++++++++ api/prometheus/{ => v1}/api.go | 293 ++++++------------- api/prometheus/{ => v1}/api_test.go | 432 +++++++++++----------------- 4 files changed, 505 insertions(+), 468 deletions(-) create mode 100644 api/client.go create mode 100644 api/client_test.go rename api/prometheus/{ => v1}/api.go (57%) rename api/prometheus/{ => v1}/api_test.go (73%) diff --git a/api/client.go b/api/client.go new file mode 100644 index 0000000..c3f6cc8 --- /dev/null +++ b/api/client.go @@ -0,0 +1,136 @@ +// Copyright 2015 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package api provides clients the HTTP API's. +package api + +import ( + "io/ioutil" + "net" + "net/http" + "net/url" + "path" + "strings" + "time" + + "golang.org/x/net/context" + "golang.org/x/net/context/ctxhttp" +) + +// CancelableTransport is like net.Transport but provides +// per-request cancelation functionality. +type CancelableTransport interface { + http.RoundTripper + CancelRequest(req *http.Request) +} + +// DefaultTransport is used if no Transport is set in Config. +var DefaultTransport CancelableTransport = &http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + TLSHandshakeTimeout: 10 * time.Second, +} + +// Config defines configuration parameters for a new client. +type Config struct { + // The address of the Prometheus to connect to. + Address string + + // Transport is used by the Client to drive HTTP requests. If not + // provided, DefaultTransport will be used. + Transport CancelableTransport +} + +func (cfg *Config) transport() CancelableTransport { + if cfg.Transport == nil { + return DefaultTransport + } + return cfg.Transport +} + +// Client is the interface for an API client. +type Client interface { + URL(ep string, args map[string]string) *url.URL + Do(context.Context, *http.Request) (*http.Response, []byte, error) +} + +// New returns a new Client. +// +// It is safe to use the returned Client from multiple goroutines. +func New(cfg Config) (Client, error) { + u, err := url.Parse(cfg.Address) + if err != nil { + return nil, err + } + u.Path = strings.TrimRight(u.Path, "/") + + return &httpClient{ + endpoint: u, + transport: cfg.transport(), + }, nil +} + +type httpClient struct { + endpoint *url.URL + transport CancelableTransport +} + +func (c *httpClient) URL(ep string, args map[string]string) *url.URL { + p := path.Join(c.endpoint.Path, ep) + + for arg, val := range args { + arg = ":" + arg + p = strings.Replace(p, arg, val, -1) + } + + u := *c.endpoint + u.Path = p + + return &u +} + +func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { + resp, err := ctxhttp.Do(ctx, &http.Client{Transport: c.transport}, req) + + defer func() { + if resp != nil { + resp.Body.Close() + } + }() + + if err != nil { + return nil, nil, err + } + + var body []byte + done := make(chan struct{}) + go func() { + body, err = ioutil.ReadAll(resp.Body) + close(done) + }() + + select { + case <-ctx.Done(): + err = resp.Body.Close() + <-done + if err == nil { + err = ctx.Err() + } + case <-done: + } + + return resp, body, err +} diff --git a/api/client_test.go b/api/client_test.go new file mode 100644 index 0000000..a068e2f --- /dev/null +++ b/api/client_test.go @@ -0,0 +1,112 @@ +// Copyright 2015 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "net/url" + "testing" +) + +func TestConfig(t *testing.T) { + c := Config{} + if c.transport() != DefaultTransport { + t.Fatalf("expected default transport for nil Transport field") + } +} + +func TestClientURL(t *testing.T) { + tests := []struct { + address string + endpoint string + args map[string]string + expected string + }{ + { + address: "http://localhost:9090", + endpoint: "/test", + expected: "http://localhost:9090/test", + }, + { + address: "http://localhost", + endpoint: "/test", + expected: "http://localhost/test", + }, + { + address: "http://localhost:9090", + endpoint: "test", + expected: "http://localhost:9090/test", + }, + { + address: "http://localhost:9090/prefix", + endpoint: "/test", + expected: "http://localhost:9090/prefix/test", + }, + { + address: "https://localhost:9090/", + endpoint: "/test/", + expected: "https://localhost:9090/test", + }, + { + address: "http://localhost:9090", + endpoint: "/test/:param", + args: map[string]string{ + "param": "content", + }, + expected: "http://localhost:9090/test/content", + }, + { + address: "http://localhost:9090", + endpoint: "/test/:param/more/:param", + args: map[string]string{ + "param": "content", + }, + expected: "http://localhost:9090/test/content/more/content", + }, + { + address: "http://localhost:9090", + endpoint: "/test/:param/more/:foo", + args: map[string]string{ + "param": "content", + "foo": "bar", + }, + expected: "http://localhost:9090/test/content/more/bar", + }, + { + address: "http://localhost:9090", + endpoint: "/test/:param", + args: map[string]string{ + "nonexistant": "content", + }, + expected: "http://localhost:9090/test/:param", + }, + } + + for _, test := range tests { + ep, err := url.Parse(test.address) + if err != nil { + t.Fatal(err) + } + + hclient := &httpClient{ + endpoint: ep, + transport: DefaultTransport, + } + + u := hclient.URL(test.endpoint, test.args) + if u.String() != test.expected { + t.Errorf("unexpected result: got %s, want %s", u, test.expected) + continue + } + } +} diff --git a/api/prometheus/api.go b/api/prometheus/v1/api.go similarity index 57% rename from api/prometheus/api.go rename to api/prometheus/v1/api.go index 61c47ad..99bc0cd 100644 --- a/api/prometheus/api.go +++ b/api/prometheus/v1/api.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Prometheus Authors +// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -11,35 +11,32 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package prometheus provides bindings to the Prometheus HTTP API: +// Package v1 provides bindings to the Prometheus HTTP API v1: // http://prometheus.io/docs/querying/api/ -package prometheus +package v1 import ( "encoding/json" "fmt" - "io/ioutil" - "net" "net/http" - "net/url" - "path" "strconv" - "strings" "time" - "github.com/prometheus/common/model" "golang.org/x/net/context" - "golang.org/x/net/context/ctxhttp" + + "github.com/prometheus/client_golang/api" + "github.com/prometheus/common/model" ) const ( statusAPIError = 422 - apiPrefix = "/api/v1" - epQuery = "/query" - epQueryRange = "/query_range" - epLabelValues = "/label/:name/values" - epSeries = "/series" + apiPrefix = "/api/v1" + + epQuery = apiPrefix + "/query" + epQueryRange = apiPrefix + "/query_range" + epLabelValues = apiPrefix + "/label/:name/values" + epSeries = apiPrefix + "/series" ) // ErrorType models the different API error types. @@ -64,168 +61,6 @@ func (e *Error) Error() string { return fmt.Sprintf("%s: %s", e.Type, e.Msg) } -// CancelableTransport is like net.Transport but provides -// per-request cancelation functionality. -type CancelableTransport interface { - http.RoundTripper - CancelRequest(req *http.Request) -} - -// DefaultTransport is used if no Transport is set in Config. -var DefaultTransport CancelableTransport = &http.Transport{ - Proxy: http.ProxyFromEnvironment, - Dial: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - }).Dial, - TLSHandshakeTimeout: 10 * time.Second, -} - -// Config defines configuration parameters for a new client. -type Config struct { - // The address of the Prometheus to connect to. - Address string - - // Transport is used by the Client to drive HTTP requests. If not - // provided, DefaultTransport will be used. - Transport CancelableTransport -} - -func (cfg *Config) transport() CancelableTransport { - if cfg.Transport == nil { - return DefaultTransport - } - return cfg.Transport -} - -// Client is the interface for an API client. -type Client interface { - url(ep string, args map[string]string) *url.URL - do(context.Context, *http.Request) (*http.Response, []byte, error) -} - -// New returns a new Client. -// -// It is safe to use the returned Client from multiple goroutines. -func New(cfg Config) (Client, error) { - u, err := url.Parse(cfg.Address) - if err != nil { - return nil, err - } - u.Path = strings.TrimRight(u.Path, "/") + apiPrefix - - return &httpClient{ - endpoint: u, - transport: cfg.transport(), - }, nil -} - -type httpClient struct { - endpoint *url.URL - transport CancelableTransport -} - -func (c *httpClient) url(ep string, args map[string]string) *url.URL { - p := path.Join(c.endpoint.Path, ep) - - for arg, val := range args { - arg = ":" + arg - p = strings.Replace(p, arg, val, -1) - } - - u := *c.endpoint - u.Path = p - - return &u -} - -func (c *httpClient) do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { - resp, err := ctxhttp.Do(ctx, &http.Client{Transport: c.transport}, req) - - defer func() { - if resp != nil { - resp.Body.Close() - } - }() - - if err != nil { - return nil, nil, err - } - - var body []byte - done := make(chan struct{}) - go func() { - body, err = ioutil.ReadAll(resp.Body) - close(done) - }() - - select { - case <-ctx.Done(): - err = resp.Body.Close() - <-done - if err == nil { - err = ctx.Err() - } - case <-done: - } - - return resp, body, err -} - -// apiClient wraps a regular client and processes successful API responses. -// Successful also includes responses that errored at the API level. -type apiClient struct { - Client -} - -type apiResponse struct { - Status string `json:"status"` - Data json.RawMessage `json:"data"` - ErrorType ErrorType `json:"errorType"` - Error string `json:"error"` -} - -func (c apiClient) do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { - resp, body, err := c.Client.do(ctx, req) - if err != nil { - return resp, body, err - } - - code := resp.StatusCode - - if code/100 != 2 && code != statusAPIError { - return resp, body, &Error{ - Type: ErrBadResponse, - Msg: fmt.Sprintf("bad response code %d", resp.StatusCode), - } - } - - var result apiResponse - - if err = json.Unmarshal(body, &result); err != nil { - return resp, body, &Error{ - Type: ErrBadResponse, - Msg: err.Error(), - } - } - - if (code == statusAPIError) != (result.Status == "error") { - err = &Error{ - Type: ErrBadResponse, - Msg: "inconsistent body for response code", - } - } - - if code == statusAPIError && result.Status == "error" { - err = &Error{ - Type: result.ErrorType, - Msg: result.Error, - } - } - - return resp, []byte(result.Data), err -} - // Range represents a sliced time range. type Range struct { // The boundaries of the time range. @@ -234,6 +69,16 @@ type Range struct { Step time.Duration } +// API provides bindings the Prometheus's v1 API. +type API interface { + // Query performs a query for the given time. + Query(ctx context.Context, query string, ts time.Time) (model.Value, error) + // QueryRange performs a query for the given range. + QueryRange(ctx context.Context, query string, r Range) (model.Value, error) + // LabelValues performs a query for the values of the given label. + LabelValues(ctx context.Context, label string) (model.LabelValues, error) +} + // queryResult contains result data for a query. type queryResult struct { Type model.ValueType `json:"resultType"` @@ -276,29 +121,19 @@ func (qr *queryResult) UnmarshalJSON(b []byte) error { return err } -// QueryAPI provides bindings the Prometheus's query API. -type QueryAPI interface { - // Query performs a query for the given time. - Query(ctx context.Context, query string, ts time.Time) (model.Value, error) - // QueryRange performs a query for the given range. - QueryRange(ctx context.Context, query string, r Range) (model.Value, error) - // LabelValues performs a query for the values of the given label. - LabelValues(ctx context.Context, label string) (model.LabelValues, error) -} - -// NewQueryAPI returns a new QueryAPI for the client. +// NewAPI returns a new API for the client. // -// It is safe to use the returned QueryAPI from multiple goroutines. -func NewQueryAPI(c Client) QueryAPI { - return &httpQueryAPI{client: apiClient{c}} +// It is safe to use the returned API from multiple goroutines. +func NewAPI(c api.Client) API { + return &httpAPI{client: apiClient{c}} } -type httpQueryAPI struct { - client Client +type httpAPI struct { + client api.Client } -func (h *httpQueryAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, error) { - u := h.client.url(epQuery, nil) +func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, error) { + u := h.client.URL(epQuery, nil) q := u.Query() q.Set("query", query) @@ -311,7 +146,7 @@ func (h *httpQueryAPI) Query(ctx context.Context, query string, ts time.Time) (m return nil, err } - _, body, err := h.client.do(ctx, req) + _, body, err := h.client.Do(ctx, req) if err != nil { return nil, err } @@ -322,8 +157,8 @@ func (h *httpQueryAPI) Query(ctx context.Context, query string, ts time.Time) (m return model.Value(qres.v), err } -func (h *httpQueryAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, error) { - u := h.client.url(epQueryRange, nil) +func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, error) { + u := h.client.URL(epQueryRange, nil) q := u.Query() var ( @@ -344,7 +179,7 @@ func (h *httpQueryAPI) QueryRange(ctx context.Context, query string, r Range) (m return nil, err } - _, body, err := h.client.do(ctx, req) + _, body, err := h.client.Do(ctx, req) if err != nil { return nil, err } @@ -355,13 +190,13 @@ func (h *httpQueryAPI) QueryRange(ctx context.Context, query string, r Range) (m return model.Value(qres.v), err } -func (h *httpQueryAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, error) { - u := h.client.url(epLabelValues, map[string]string{"name": label}) +func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, error) { + u := h.client.URL(epLabelValues, map[string]string{"name": label}) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { return nil, err } - _, body, err := h.client.do(ctx, req) + _, body, err := h.client.Do(ctx, req) if err != nil { return nil, err } @@ -369,3 +204,57 @@ func (h *httpQueryAPI) LabelValues(ctx context.Context, label string) (model.Lab err = json.Unmarshal(body, &labelValues) return labelValues, err } + +// apiClient wraps a regular client and processes successful API responses. +// Successful also includes responses that errored at the API level. +type apiClient struct { + api.Client +} + +type apiResponse struct { + Status string `json:"status"` + Data json.RawMessage `json:"data"` + ErrorType ErrorType `json:"errorType"` + Error string `json:"error"` +} + +func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { + resp, body, err := c.Client.Do(ctx, req) + if err != nil { + return resp, body, err + } + + code := resp.StatusCode + + if code/100 != 2 && code != statusAPIError { + return resp, body, &Error{ + Type: ErrBadResponse, + Msg: fmt.Sprintf("bad response code %d", resp.StatusCode), + } + } + + var result apiResponse + + if err = json.Unmarshal(body, &result); err != nil { + return resp, body, &Error{ + Type: ErrBadResponse, + Msg: err.Error(), + } + } + + if (code == statusAPIError) != (result.Status == "error") { + err = &Error{ + Type: ErrBadResponse, + Msg: "inconsistent body for response code", + } + } + + if code == statusAPIError && result.Status == "error" { + err = &Error{ + Type: result.ErrorType, + Msg: result.Error, + } + } + + return resp, []byte(result.Data), err +} diff --git a/api/prometheus/api_test.go b/api/prometheus/v1/api_test.go similarity index 73% rename from api/prometheus/api_test.go rename to api/prometheus/v1/api_test.go index f7ffc7c..57a9192 100644 --- a/api/prometheus/api_test.go +++ b/api/prometheus/v1/api_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Prometheus Authors +// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package prometheus +package v1 import ( "encoding/json" @@ -23,107 +23,190 @@ import ( "testing" "time" - "github.com/prometheus/common/model" "golang.org/x/net/context" + + "github.com/prometheus/common/model" ) -func TestConfig(t *testing.T) { - c := Config{} - if c.transport() != DefaultTransport { - t.Fatalf("expected default transport for nil Transport field") - } +type apiTest struct { + do func() (interface{}, error) + inErr error + inRes interface{} + + reqPath string + reqParam url.Values + reqMethod string + res interface{} + err error } -func TestClientURL(t *testing.T) { - tests := []struct { - address string - endpoint string - args map[string]string - expected string - }{ +type apiTestClient struct { + *testing.T + curTest apiTest +} + +func (c *apiTestClient) URL(ep string, args map[string]string) *url.URL { + path := ep + for k, v := range args { + path = strings.Replace(path, ":"+k, v, -1) + } + u := &url.URL{ + Host: "test:9090", + Path: path, + } + return u +} + +func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { + + test := c.curTest + + if req.URL.Path != test.reqPath { + c.Errorf("unexpected request path: want %s, got %s", test.reqPath, req.URL.Path) + } + if req.Method != test.reqMethod { + c.Errorf("unexpected request method: want %s, got %s", test.reqMethod, req.Method) + } + + b, err := json.Marshal(test.inRes) + if err != nil { + c.Fatal(err) + } + + resp := &http.Response{} + if test.inErr != nil { + resp.StatusCode = statusAPIError + } else { + resp.StatusCode = http.StatusOK + } + + return resp, b, test.inErr +} + +func TestAPIs(t *testing.T) { + + testTime := time.Now() + + client := &apiTestClient{T: t} + + queryAPI := &httpAPI{ + client: client, + } + + doQuery := func(q string, ts time.Time) func() (interface{}, error) { + return func() (interface{}, error) { + return queryAPI.Query(context.Background(), q, ts) + } + } + + doQueryRange := func(q string, rng Range) func() (interface{}, error) { + return func() (interface{}, error) { + return queryAPI.QueryRange(context.Background(), q, rng) + } + } + + doLabelValues := func(label string) func() (interface{}, error) { + return func() (interface{}, error) { + return queryAPI.LabelValues(context.Background(), label) + } + } + + queryTests := []apiTest{ { - address: "http://localhost:9090", - endpoint: "/test", - expected: "http://localhost:9090/test", - }, - { - address: "http://localhost", - endpoint: "/test", - expected: "http://localhost/test", - }, - { - address: "http://localhost:9090", - endpoint: "test", - expected: "http://localhost:9090/test", - }, - { - address: "http://localhost:9090/prefix", - endpoint: "/test", - expected: "http://localhost:9090/prefix/test", - }, - { - address: "https://localhost:9090/", - endpoint: "/test/", - expected: "https://localhost:9090/test", - }, - { - address: "http://localhost:9090", - endpoint: "/test/:param", - args: map[string]string{ - "param": "content", + do: doQuery("2", testTime), + inRes: &queryResult{ + Type: model.ValScalar, + Result: &model.Scalar{ + Value: 2, + Timestamp: model.TimeFromUnix(testTime.Unix()), + }, + }, + + reqMethod: "GET", + reqPath: "/api/v1/query", + reqParam: url.Values{ + "query": []string{"2"}, + "time": []string{testTime.Format(time.RFC3339Nano)}, + }, + res: &model.Scalar{ + Value: 2, + Timestamp: model.TimeFromUnix(testTime.Unix()), }, - expected: "http://localhost:9090/test/content", }, { - address: "http://localhost:9090", - endpoint: "/test/:param/more/:param", - args: map[string]string{ - "param": "content", + do: doQuery("2", testTime), + inErr: fmt.Errorf("some error"), + + reqMethod: "GET", + reqPath: "/api/v1/query", + reqParam: url.Values{ + "query": []string{"2"}, + "time": []string{testTime.Format(time.RFC3339Nano)}, }, - expected: "http://localhost:9090/test/content/more/content", + err: fmt.Errorf("some error"), }, + { - address: "http://localhost:9090", - endpoint: "/test/:param/more/:foo", - args: map[string]string{ - "param": "content", - "foo": "bar", + do: doQueryRange("2", Range{ + Start: testTime.Add(-time.Minute), + End: testTime, + Step: time.Minute, + }), + inErr: fmt.Errorf("some error"), + + reqMethod: "GET", + reqPath: "/api/v1/query_range", + reqParam: url.Values{ + "query": []string{"2"}, + "start": []string{testTime.Add(-time.Minute).Format(time.RFC3339Nano)}, + "end": []string{testTime.Format(time.RFC3339Nano)}, + "step": []string{time.Minute.String()}, }, - expected: "http://localhost:9090/test/content/more/bar", + err: fmt.Errorf("some error"), }, + { - address: "http://localhost:9090", - endpoint: "/test/:param", - args: map[string]string{ - "nonexistant": "content", - }, - expected: "http://localhost:9090/test/:param", + do: doLabelValues("mylabel"), + inRes: []string{"val1", "val2"}, + reqMethod: "GET", + reqPath: "/api/v1/label/mylabel/values", + res: model.LabelValues{"val1", "val2"}, + }, + + { + do: doLabelValues("mylabel"), + inErr: fmt.Errorf("some error"), + reqMethod: "GET", + reqPath: "/api/v1/label/mylabel/values", + err: fmt.Errorf("some error"), }, } + var tests []apiTest + tests = append(tests, queryTests...) + for _, test := range tests { - ep, err := url.Parse(test.address) + client.curTest = test + + res, err := test.do() + + if test.err != nil { + if err == nil { + t.Errorf("expected error %q but got none", test.err) + continue + } + if err.Error() != test.err.Error() { + t.Errorf("unexpected error: want %s, got %s", test.err, err) + } + continue + } if err != nil { - t.Fatal(err) - } - - hclient := &httpClient{ - endpoint: ep, - transport: DefaultTransport, - } - - u := hclient.url(test.endpoint, test.args) - if u.String() != test.expected { - t.Errorf("unexpected result: got %s, want %s", u, test.expected) + t.Errorf("unexpected error: %s", err) continue } - // The apiClient must return exactly the same result as the httpClient. - aclient := &apiClient{hclient} - - u = aclient.url(test.endpoint, test.args) - if u.String() != test.expected { - t.Errorf("unexpected result: got %s, want %s", u, test.expected) + if !reflect.DeepEqual(res, test.res) { + t.Errorf("unexpected result: want %v, got %v", test.res, res) } } } @@ -142,11 +225,11 @@ type apiClientTest struct { err *Error } -func (c *testClient) url(ep string, args map[string]string) *url.URL { +func (c *testClient) URL(ep string, args map[string]string) *url.URL { return nil } -func (c *testClient) do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { +func (c *testClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { if ctx == nil { c.Fatalf("context was not passed down") } @@ -272,7 +355,7 @@ func TestAPIClientDo(t *testing.T) { tc.ch <- test - _, body, err := client.do(context.Background(), tc.req) + _, body, err := client.Do(context.Background(), tc.req) if test.err != nil { if err == nil { @@ -295,186 +378,3 @@ func TestAPIClientDo(t *testing.T) { } } } - -type apiTestClient struct { - *testing.T - curTest apiTest -} - -type apiTest struct { - do func() (interface{}, error) - inErr error - inRes interface{} - - reqPath string - reqParam url.Values - reqMethod string - res interface{} - err error -} - -func (c *apiTestClient) url(ep string, args map[string]string) *url.URL { - path := apiPrefix + ep - for k, v := range args { - path = strings.Replace(path, ":"+k, v, -1) - } - u := &url.URL{ - Host: "test:9090", - Path: path, - } - return u -} - -func (c *apiTestClient) do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { - - test := c.curTest - - if req.URL.Path != test.reqPath { - c.Errorf("unexpected request path: want %s, got %s", test.reqPath, req.URL.Path) - } - if req.Method != test.reqMethod { - c.Errorf("unexpected request method: want %s, got %s", test.reqMethod, req.Method) - } - - b, err := json.Marshal(test.inRes) - if err != nil { - c.Fatal(err) - } - - resp := &http.Response{} - if test.inErr != nil { - resp.StatusCode = statusAPIError - } else { - resp.StatusCode = http.StatusOK - } - - return resp, b, test.inErr -} - -func TestAPIs(t *testing.T) { - - testTime := time.Now() - - client := &apiTestClient{T: t} - - queryAPI := &httpQueryAPI{ - client: client, - } - - doQuery := func(q string, ts time.Time) func() (interface{}, error) { - return func() (interface{}, error) { - return queryAPI.Query(context.Background(), q, ts) - } - } - - doQueryRange := func(q string, rng Range) func() (interface{}, error) { - return func() (interface{}, error) { - return queryAPI.QueryRange(context.Background(), q, rng) - } - } - - doLabelValues := func(label string) func() (interface{}, error) { - return func() (interface{}, error) { - return queryAPI.LabelValues(context.Background(), label) - } - } - - queryTests := []apiTest{ - { - do: doQuery("2", testTime), - inRes: &queryResult{ - Type: model.ValScalar, - Result: &model.Scalar{ - Value: 2, - Timestamp: model.TimeFromUnix(testTime.Unix()), - }, - }, - - reqMethod: "GET", - reqPath: "/api/v1/query", - reqParam: url.Values{ - "query": []string{"2"}, - "time": []string{testTime.Format(time.RFC3339Nano)}, - }, - res: &model.Scalar{ - Value: 2, - Timestamp: model.TimeFromUnix(testTime.Unix()), - }, - }, - { - do: doQuery("2", testTime), - inErr: fmt.Errorf("some error"), - - reqMethod: "GET", - reqPath: "/api/v1/query", - reqParam: url.Values{ - "query": []string{"2"}, - "time": []string{testTime.Format(time.RFC3339Nano)}, - }, - err: fmt.Errorf("some error"), - }, - - { - do: doQueryRange("2", Range{ - Start: testTime.Add(-time.Minute), - End: testTime, - Step: time.Minute, - }), - inErr: fmt.Errorf("some error"), - - reqMethod: "GET", - reqPath: "/api/v1/query_range", - reqParam: url.Values{ - "query": []string{"2"}, - "start": []string{testTime.Add(-time.Minute).Format(time.RFC3339Nano)}, - "end": []string{testTime.Format(time.RFC3339Nano)}, - "step": []string{time.Minute.String()}, - }, - err: fmt.Errorf("some error"), - }, - - { - do: doLabelValues("mylabel"), - inRes: []string{"val1", "val2"}, - reqMethod: "GET", - reqPath: "/api/v1/label/mylabel/values", - res: model.LabelValues{"val1", "val2"}, - }, - - { - do: doLabelValues("mylabel"), - inErr: fmt.Errorf("some error"), - reqMethod: "GET", - reqPath: "/api/v1/label/mylabel/values", - err: fmt.Errorf("some error"), - }, - } - - var tests []apiTest - tests = append(tests, queryTests...) - - for _, test := range tests { - client.curTest = test - - res, err := test.do() - - if test.err != nil { - if err == nil { - t.Errorf("expected error %q but got none", test.err) - continue - } - if err.Error() != test.err.Error() { - t.Errorf("unexpected error: want %s, got %s", test.err, err) - } - continue - } - if err != nil { - t.Errorf("unexpected error: %s", err) - continue - } - - if !reflect.DeepEqual(res, test.res) { - t.Errorf("unexpected result: want %v, got %v", test.res, res) - } - } -} From 11fae2ef0cd316719760faf184a09b7e047e0ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carvalho?= Date: Thu, 20 Apr 2017 09:57:46 -0300 Subject: [PATCH 07/11] api: uses stdlib context --- api/client.go | 2 +- api/prometheus/v1/api.go | 3 +-- api/prometheus/v1/api_test.go | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/api/client.go b/api/client.go index c3f6cc8..1aa1591 100644 --- a/api/client.go +++ b/api/client.go @@ -15,6 +15,7 @@ package api import ( + "context" "io/ioutil" "net" "net/http" @@ -23,7 +24,6 @@ import ( "strings" "time" - "golang.org/x/net/context" "golang.org/x/net/context/ctxhttp" ) diff --git a/api/prometheus/v1/api.go b/api/prometheus/v1/api.go index 99bc0cd..d849b09 100644 --- a/api/prometheus/v1/api.go +++ b/api/prometheus/v1/api.go @@ -16,14 +16,13 @@ package v1 import ( + "context" "encoding/json" "fmt" "net/http" "strconv" "time" - "golang.org/x/net/context" - "github.com/prometheus/client_golang/api" "github.com/prometheus/common/model" ) diff --git a/api/prometheus/v1/api_test.go b/api/prometheus/v1/api_test.go index 57a9192..0b7a635 100644 --- a/api/prometheus/v1/api_test.go +++ b/api/prometheus/v1/api_test.go @@ -14,6 +14,7 @@ package v1 import ( + "context" "encoding/json" "fmt" "net/http" @@ -23,8 +24,6 @@ import ( "testing" "time" - "golang.org/x/net/context" - "github.com/prometheus/common/model" ) From 09dcce7042ead90d3b7c1d628bb2dbf802871a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carvalho?= Date: Thu, 20 Apr 2017 10:31:18 -0300 Subject: [PATCH 08/11] api: uses context from net/http This commit removes the now unnecessary CancelableTransport and rely on the net/http context support. --- api/client.go | 41 +++++++++++++++++------------------------ api/client_test.go | 9 +++++---- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/api/client.go b/api/client.go index 1aa1591..a6ef8f7 100644 --- a/api/client.go +++ b/api/client.go @@ -23,19 +23,10 @@ import ( "path" "strings" "time" - - "golang.org/x/net/context/ctxhttp" ) -// CancelableTransport is like net.Transport but provides -// per-request cancelation functionality. -type CancelableTransport interface { - http.RoundTripper - CancelRequest(req *http.Request) -} - -// DefaultTransport is used if no Transport is set in Config. -var DefaultTransport CancelableTransport = &http.Transport{ +// DefaultRoundTripper is used if no RoundTripper is set in Config. +var DefaultRoundTripper http.RoundTripper = &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: 30 * time.Second, @@ -49,16 +40,16 @@ type Config struct { // The address of the Prometheus to connect to. Address string - // Transport is used by the Client to drive HTTP requests. If not - // provided, DefaultTransport will be used. - Transport CancelableTransport + // RoundTripper is used by the Client to drive HTTP requests. If not + // provided, DefaultRoundTripper will be used. + RoundTripper http.RoundTripper } -func (cfg *Config) transport() CancelableTransport { - if cfg.Transport == nil { - return DefaultTransport +func (cfg *Config) roundTripper() http.RoundTripper { + if cfg.RoundTripper == nil { + return DefaultRoundTripper } - return cfg.Transport + return cfg.RoundTripper } // Client is the interface for an API client. @@ -78,14 +69,14 @@ func New(cfg Config) (Client, error) { u.Path = strings.TrimRight(u.Path, "/") return &httpClient{ - endpoint: u, - transport: cfg.transport(), + endpoint: u, + client: http.Client{Transport: cfg.roundTripper()}, }, nil } type httpClient struct { - endpoint *url.URL - transport CancelableTransport + endpoint *url.URL + client http.Client } func (c *httpClient) URL(ep string, args map[string]string) *url.URL { @@ -103,8 +94,10 @@ func (c *httpClient) URL(ep string, args map[string]string) *url.URL { } func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { - resp, err := ctxhttp.Do(ctx, &http.Client{Transport: c.transport}, req) - + if ctx != nil { + req = req.WithContext(ctx) + } + resp, err := c.client.Do(req) defer func() { if resp != nil { resp.Body.Close() diff --git a/api/client_test.go b/api/client_test.go index a068e2f..8db4f76 100644 --- a/api/client_test.go +++ b/api/client_test.go @@ -14,14 +14,15 @@ package api import ( + "net/http" "net/url" "testing" ) func TestConfig(t *testing.T) { c := Config{} - if c.transport() != DefaultTransport { - t.Fatalf("expected default transport for nil Transport field") + if c.roundTripper() != DefaultRoundTripper { + t.Fatalf("expected default roundtripper for nil RoundTripper field") } } @@ -99,8 +100,8 @@ func TestClientURL(t *testing.T) { } hclient := &httpClient{ - endpoint: ep, - transport: DefaultTransport, + endpoint: ep, + client: http.Client{Transport: DefaultRoundTripper}, } u := hclient.URL(test.endpoint, test.args) From 6fdb46821292c5b7e2b417e5ad18e20b829161c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carvalho?= Date: Mon, 24 Apr 2017 18:05:17 -0300 Subject: [PATCH 09/11] api: add build tag for go 1.7 --- api/client.go | 2 ++ api/client_test.go | 2 ++ api/prometheus/v1/api.go | 2 ++ api/prometheus/v1/api_test.go | 2 ++ 4 files changed, 8 insertions(+) diff --git a/api/client.go b/api/client.go index a6ef8f7..513e455 100644 --- a/api/client.go +++ b/api/client.go @@ -11,6 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// +build go1.7 + // Package api provides clients the HTTP API's. package api diff --git a/api/client_test.go b/api/client_test.go index 8db4f76..53226d7 100644 --- a/api/client_test.go +++ b/api/client_test.go @@ -11,6 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// +build go1.7 + package api import ( diff --git a/api/prometheus/v1/api.go b/api/prometheus/v1/api.go index d849b09..421f181 100644 --- a/api/prometheus/v1/api.go +++ b/api/prometheus/v1/api.go @@ -11,6 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// +build go1.7 + // Package v1 provides bindings to the Prometheus HTTP API v1: // http://prometheus.io/docs/querying/api/ package v1 diff --git a/api/prometheus/v1/api_test.go b/api/prometheus/v1/api_test.go index 0b7a635..2c8b1b2 100644 --- a/api/prometheus/v1/api_test.go +++ b/api/prometheus/v1/api_test.go @@ -11,6 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// +build go1.7 + package v1 import ( From 5d19d9de3457908d3efc1d635f85018c7a0a12fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carvalho?= Date: Tue, 25 Apr 2017 11:37:13 -0300 Subject: [PATCH 10/11] api: renames create client func --- api/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/client.go b/api/client.go index 513e455..6115e6a 100644 --- a/api/client.go +++ b/api/client.go @@ -60,10 +60,10 @@ type Client interface { Do(context.Context, *http.Request) (*http.Response, []byte, error) } -// New returns a new Client. +// NewClient returns a new Client. // // It is safe to use the returned Client from multiple goroutines. -func New(cfg Config) (Client, error) { +func NewClient(cfg Config) (Client, error) { u, err := url.Parse(cfg.Address) if err != nil { return nil, err From 8077e4bd69dce5454713bd88446e63a09ada7260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carvalho?= Date: Tue, 25 Apr 2017 18:15:57 -0300 Subject: [PATCH 11/11] api: fix typo in comments --- api/client.go | 2 +- api/prometheus/v1/api.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/client.go b/api/client.go index 6115e6a..bf26724 100644 --- a/api/client.go +++ b/api/client.go @@ -13,7 +13,7 @@ // +build go1.7 -// Package api provides clients the HTTP API's. +// Package api provides clients for the HTTP APIs. package api import ( diff --git a/api/prometheus/v1/api.go b/api/prometheus/v1/api.go index 421f181..734a12e 100644 --- a/api/prometheus/v1/api.go +++ b/api/prometheus/v1/api.go @@ -70,7 +70,7 @@ type Range struct { Step time.Duration } -// API provides bindings the Prometheus's v1 API. +// API provides bindings for Prometheus's v1 API. type API interface { // Query performs a query for the given time. Query(ctx context.Context, query string, ts time.Time) (model.Value, error)