api.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  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
  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. // Package prometheus provides bindings to the Prometheus HTTP API:
  14. // http://prometheus.io/docs/querying/api/
  15. package prometheus
  16. import (
  17. "encoding/json"
  18. "fmt"
  19. "io/ioutil"
  20. "net"
  21. "net/http"
  22. "net/url"
  23. "path"
  24. "strconv"
  25. "strings"
  26. "time"
  27. "github.com/prometheus/common/model"
  28. "golang.org/x/net/context"
  29. "golang.org/x/net/context/ctxhttp"
  30. )
  31. const (
  32. statusAPIError = 422
  33. apiPrefix = "/api/v1"
  34. epQuery = "/query"
  35. epQueryRange = "/query_range"
  36. epLabelValues = "/label/:name/values"
  37. epSeries = "/series"
  38. )
  39. // ErrorType models the different API error types.
  40. type ErrorType string
  41. // Possible values for ErrorType.
  42. const (
  43. ErrBadData ErrorType = "bad_data"
  44. ErrTimeout = "timeout"
  45. ErrCanceled = "canceled"
  46. ErrExec = "execution"
  47. ErrBadResponse = "bad_response"
  48. )
  49. // Error is an error returned by the API.
  50. type Error struct {
  51. Type ErrorType
  52. Msg string
  53. }
  54. func (e *Error) Error() string {
  55. return fmt.Sprintf("%s: %s", e.Type, e.Msg)
  56. }
  57. // CancelableTransport is like net.Transport but provides
  58. // per-request cancelation functionality.
  59. type CancelableTransport interface {
  60. http.RoundTripper
  61. CancelRequest(req *http.Request)
  62. }
  63. // DefaultTransport is used if no Transport is set in Config.
  64. var DefaultTransport CancelableTransport = &http.Transport{
  65. Proxy: http.ProxyFromEnvironment,
  66. Dial: (&net.Dialer{
  67. Timeout: 30 * time.Second,
  68. KeepAlive: 30 * time.Second,
  69. }).Dial,
  70. TLSHandshakeTimeout: 10 * time.Second,
  71. }
  72. // Config defines configuration parameters for a new client.
  73. type Config struct {
  74. // The address of the Prometheus to connect to.
  75. Address string
  76. // Transport is used by the Client to drive HTTP requests. If not
  77. // provided, DefaultTransport will be used.
  78. Transport CancelableTransport
  79. }
  80. func (cfg *Config) transport() CancelableTransport {
  81. if cfg.Transport == nil {
  82. return DefaultTransport
  83. }
  84. return cfg.Transport
  85. }
  86. // Client is the interface for an API client.
  87. type Client interface {
  88. url(ep string, args map[string]string) *url.URL
  89. do(context.Context, *http.Request) (*http.Response, []byte, error)
  90. }
  91. // New returns a new Client.
  92. //
  93. // It is safe to use the returned Client from multiple goroutines.
  94. func New(cfg Config) (Client, error) {
  95. u, err := url.Parse(cfg.Address)
  96. if err != nil {
  97. return nil, err
  98. }
  99. u.Path = strings.TrimRight(u.Path, "/") + apiPrefix
  100. return &httpClient{
  101. endpoint: u,
  102. transport: cfg.transport(),
  103. }, nil
  104. }
  105. type httpClient struct {
  106. endpoint *url.URL
  107. transport CancelableTransport
  108. }
  109. func (c *httpClient) url(ep string, args map[string]string) *url.URL {
  110. p := path.Join(c.endpoint.Path, ep)
  111. for arg, val := range args {
  112. arg = ":" + arg
  113. p = strings.Replace(p, arg, val, -1)
  114. }
  115. u := *c.endpoint
  116. u.Path = p
  117. return &u
  118. }
  119. func (c *httpClient) do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
  120. resp, err := ctxhttp.Do(ctx, &http.Client{Transport: c.transport}, req)
  121. defer func() {
  122. if resp != nil {
  123. resp.Body.Close()
  124. }
  125. }()
  126. if err != nil {
  127. return nil, nil, err
  128. }
  129. var body []byte
  130. done := make(chan struct{})
  131. go func() {
  132. body, err = ioutil.ReadAll(resp.Body)
  133. close(done)
  134. }()
  135. select {
  136. case <-ctx.Done():
  137. err = resp.Body.Close()
  138. <-done
  139. if err == nil {
  140. err = ctx.Err()
  141. }
  142. case <-done:
  143. }
  144. return resp, body, err
  145. }
  146. // apiClient wraps a regular client and processes successful API responses.
  147. // Successful also includes responses that errored at the API level.
  148. type apiClient struct {
  149. Client
  150. }
  151. type apiResponse struct {
  152. Status string `json:"status"`
  153. Data json.RawMessage `json:"data"`
  154. ErrorType ErrorType `json:"errorType"`
  155. Error string `json:"error"`
  156. }
  157. func (c apiClient) do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
  158. resp, body, err := c.Client.do(ctx, req)
  159. if err != nil {
  160. return resp, body, err
  161. }
  162. code := resp.StatusCode
  163. if code/100 != 2 && code != statusAPIError {
  164. return resp, body, &Error{
  165. Type: ErrBadResponse,
  166. Msg: fmt.Sprintf("bad response code %d", resp.StatusCode),
  167. }
  168. }
  169. var result apiResponse
  170. if err = json.Unmarshal(body, &result); err != nil {
  171. return resp, body, &Error{
  172. Type: ErrBadResponse,
  173. Msg: err.Error(),
  174. }
  175. }
  176. if (code == statusAPIError) != (result.Status == "error") {
  177. err = &Error{
  178. Type: ErrBadResponse,
  179. Msg: "inconsistent body for response code",
  180. }
  181. }
  182. if code == statusAPIError && result.Status == "error" {
  183. err = &Error{
  184. Type: result.ErrorType,
  185. Msg: result.Error,
  186. }
  187. }
  188. return resp, []byte(result.Data), err
  189. }
  190. // Range represents a sliced time range.
  191. type Range struct {
  192. // The boundaries of the time range.
  193. Start, End time.Time
  194. // The maximum time between two slices within the boundaries.
  195. Step time.Duration
  196. }
  197. // queryResult contains result data for a query.
  198. type queryResult struct {
  199. Type model.ValueType `json:"resultType"`
  200. Result interface{} `json:"result"`
  201. // The decoded value.
  202. v model.Value
  203. }
  204. func (qr *queryResult) UnmarshalJSON(b []byte) error {
  205. v := struct {
  206. Type model.ValueType `json:"resultType"`
  207. Result json.RawMessage `json:"result"`
  208. }{}
  209. err := json.Unmarshal(b, &v)
  210. if err != nil {
  211. return err
  212. }
  213. switch v.Type {
  214. case model.ValScalar:
  215. var sv model.Scalar
  216. err = json.Unmarshal(v.Result, &sv)
  217. qr.v = &sv
  218. case model.ValVector:
  219. var vv model.Vector
  220. err = json.Unmarshal(v.Result, &vv)
  221. qr.v = vv
  222. case model.ValMatrix:
  223. var mv model.Matrix
  224. err = json.Unmarshal(v.Result, &mv)
  225. qr.v = mv
  226. default:
  227. err = fmt.Errorf("unexpected value type %q", v.Type)
  228. }
  229. return err
  230. }
  231. // QueryAPI provides bindings the Prometheus's query API.
  232. type QueryAPI interface {
  233. // Query performs a query for the given time.
  234. Query(ctx context.Context, query string, ts time.Time) (model.Value, error)
  235. // Query performs a query for the given range.
  236. QueryRange(ctx context.Context, query string, r Range) (model.Value, error)
  237. }
  238. // NewQueryAPI returns a new QueryAPI for the client.
  239. //
  240. // It is safe to use the returned QueryAPI from multiple goroutines.
  241. func NewQueryAPI(c Client) QueryAPI {
  242. return &httpQueryAPI{client: apiClient{c}}
  243. }
  244. type httpQueryAPI struct {
  245. client Client
  246. }
  247. func (h *httpQueryAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, error) {
  248. u := h.client.url(epQuery, nil)
  249. q := u.Query()
  250. q.Set("query", query)
  251. q.Set("time", ts.Format(time.RFC3339Nano))
  252. u.RawQuery = q.Encode()
  253. req, _ := http.NewRequest("GET", u.String(), nil)
  254. _, body, err := h.client.do(ctx, req)
  255. if err != nil {
  256. return nil, err
  257. }
  258. var qres queryResult
  259. err = json.Unmarshal(body, &qres)
  260. return model.Value(qres.v), err
  261. }
  262. func (h *httpQueryAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, error) {
  263. u := h.client.url(epQueryRange, nil)
  264. q := u.Query()
  265. var (
  266. start = r.Start.Format(time.RFC3339Nano)
  267. end = r.End.Format(time.RFC3339Nano)
  268. step = strconv.FormatFloat(r.Step.Seconds(), 'f', 3, 64)
  269. )
  270. q.Set("query", query)
  271. q.Set("start", start)
  272. q.Set("end", end)
  273. q.Set("step", step)
  274. u.RawQuery = q.Encode()
  275. req, _ := http.NewRequest("GET", u.String(), nil)
  276. _, body, err := h.client.do(ctx, req)
  277. if err != nil {
  278. return nil, err
  279. }
  280. var qres queryResult
  281. err = json.Unmarshal(body, &qres)
  282. return model.Value(qres.v), err
  283. }