api.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  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. // +build go1.7
  14. // Package v1 provides bindings to the Prometheus HTTP API v1:
  15. // http://prometheus.io/docs/querying/api/
  16. package v1
  17. import (
  18. "context"
  19. "encoding/json"
  20. "fmt"
  21. "net/http"
  22. "strconv"
  23. "time"
  24. "github.com/prometheus/client_golang/api"
  25. "github.com/prometheus/common/model"
  26. )
  27. const (
  28. statusAPIError = 422
  29. apiPrefix = "/api/v1"
  30. epQuery = apiPrefix + "/query"
  31. epQueryRange = apiPrefix + "/query_range"
  32. epLabelValues = apiPrefix + "/label/:name/values"
  33. epSeries = apiPrefix + "/series"
  34. )
  35. // ErrorType models the different API error types.
  36. type ErrorType string
  37. // Possible values for ErrorType.
  38. const (
  39. ErrBadData ErrorType = "bad_data"
  40. ErrTimeout = "timeout"
  41. ErrCanceled = "canceled"
  42. ErrExec = "execution"
  43. ErrBadResponse = "bad_response"
  44. )
  45. // Error is an error returned by the API.
  46. type Error struct {
  47. Type ErrorType
  48. Msg string
  49. }
  50. func (e *Error) Error() string {
  51. return fmt.Sprintf("%s: %s", e.Type, e.Msg)
  52. }
  53. // Range represents a sliced time range.
  54. type Range struct {
  55. // The boundaries of the time range.
  56. Start, End time.Time
  57. // The maximum time between two slices within the boundaries.
  58. Step time.Duration
  59. }
  60. // API provides bindings for Prometheus's v1 API.
  61. type API interface {
  62. // Query performs a query for the given time.
  63. Query(ctx context.Context, query string, ts time.Time) (model.Value, error)
  64. // QueryRange performs a query for the given range.
  65. QueryRange(ctx context.Context, query string, r Range) (model.Value, error)
  66. // LabelValues performs a query for the values of the given label.
  67. LabelValues(ctx context.Context, label string) (model.LabelValues, error)
  68. }
  69. // queryResult contains result data for a query.
  70. type queryResult struct {
  71. Type model.ValueType `json:"resultType"`
  72. Result interface{} `json:"result"`
  73. // The decoded value.
  74. v model.Value
  75. }
  76. func (qr *queryResult) UnmarshalJSON(b []byte) error {
  77. v := struct {
  78. Type model.ValueType `json:"resultType"`
  79. Result json.RawMessage `json:"result"`
  80. }{}
  81. err := json.Unmarshal(b, &v)
  82. if err != nil {
  83. return err
  84. }
  85. switch v.Type {
  86. case model.ValScalar:
  87. var sv model.Scalar
  88. err = json.Unmarshal(v.Result, &sv)
  89. qr.v = &sv
  90. case model.ValVector:
  91. var vv model.Vector
  92. err = json.Unmarshal(v.Result, &vv)
  93. qr.v = vv
  94. case model.ValMatrix:
  95. var mv model.Matrix
  96. err = json.Unmarshal(v.Result, &mv)
  97. qr.v = mv
  98. default:
  99. err = fmt.Errorf("unexpected value type %q", v.Type)
  100. }
  101. return err
  102. }
  103. // NewAPI returns a new API for the client.
  104. //
  105. // It is safe to use the returned API from multiple goroutines.
  106. func NewAPI(c api.Client) API {
  107. return &httpAPI{client: apiClient{c}}
  108. }
  109. type httpAPI struct {
  110. client api.Client
  111. }
  112. func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, error) {
  113. u := h.client.URL(epQuery, nil)
  114. q := u.Query()
  115. q.Set("query", query)
  116. q.Set("time", ts.Format(time.RFC3339Nano))
  117. u.RawQuery = q.Encode()
  118. req, err := http.NewRequest("GET", u.String(), nil)
  119. if err != nil {
  120. return nil, err
  121. }
  122. _, body, err := h.client.Do(ctx, req)
  123. if err != nil {
  124. return nil, err
  125. }
  126. var qres queryResult
  127. err = json.Unmarshal(body, &qres)
  128. return model.Value(qres.v), err
  129. }
  130. func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, error) {
  131. u := h.client.URL(epQueryRange, nil)
  132. q := u.Query()
  133. var (
  134. start = r.Start.Format(time.RFC3339Nano)
  135. end = r.End.Format(time.RFC3339Nano)
  136. step = strconv.FormatFloat(r.Step.Seconds(), 'f', 3, 64)
  137. )
  138. q.Set("query", query)
  139. q.Set("start", start)
  140. q.Set("end", end)
  141. q.Set("step", step)
  142. u.RawQuery = q.Encode()
  143. req, err := http.NewRequest("GET", u.String(), nil)
  144. if err != nil {
  145. return nil, err
  146. }
  147. _, body, err := h.client.Do(ctx, req)
  148. if err != nil {
  149. return nil, err
  150. }
  151. var qres queryResult
  152. err = json.Unmarshal(body, &qres)
  153. return model.Value(qres.v), err
  154. }
  155. func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, error) {
  156. u := h.client.URL(epLabelValues, map[string]string{"name": label})
  157. req, err := http.NewRequest(http.MethodGet, u.String(), nil)
  158. if err != nil {
  159. return nil, err
  160. }
  161. _, body, err := h.client.Do(ctx, req)
  162. if err != nil {
  163. return nil, err
  164. }
  165. var labelValues model.LabelValues
  166. err = json.Unmarshal(body, &labelValues)
  167. return labelValues, err
  168. }
  169. // apiClient wraps a regular client and processes successful API responses.
  170. // Successful also includes responses that errored at the API level.
  171. type apiClient struct {
  172. api.Client
  173. }
  174. type apiResponse struct {
  175. Status string `json:"status"`
  176. Data json.RawMessage `json:"data"`
  177. ErrorType ErrorType `json:"errorType"`
  178. Error string `json:"error"`
  179. }
  180. func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
  181. resp, body, err := c.Client.Do(ctx, req)
  182. if err != nil {
  183. return resp, body, err
  184. }
  185. code := resp.StatusCode
  186. if code/100 != 2 && code != statusAPIError {
  187. return resp, body, &Error{
  188. Type: ErrBadResponse,
  189. Msg: fmt.Sprintf("bad response code %d", resp.StatusCode),
  190. }
  191. }
  192. var result apiResponse
  193. if err = json.Unmarshal(body, &result); err != nil {
  194. return resp, body, &Error{
  195. Type: ErrBadResponse,
  196. Msg: err.Error(),
  197. }
  198. }
  199. if (code == statusAPIError) != (result.Status == "error") {
  200. err = &Error{
  201. Type: ErrBadResponse,
  202. Msg: "inconsistent body for response code",
  203. }
  204. }
  205. if code == statusAPIError && result.Status == "error" {
  206. err = &Error{
  207. Type: result.ErrorType,
  208. Msg: result.Error,
  209. }
  210. }
  211. return resp, []byte(result.Data), err
  212. }