diff --git a/api/prometheus/v1/api.go b/api/prometheus/v1/api.go index 627bc4d..0c8de07 100644 --- a/api/prometheus/v1/api.go +++ b/api/prometheus/v1/api.go @@ -123,6 +123,7 @@ const ( epAlertManagers = apiPrefix + "/alertmanagers" epQuery = apiPrefix + "/query" epQueryRange = apiPrefix + "/query_range" + epQueryExemplars = apiPrefix + "/query_exemplars" epLabels = apiPrefix + "/labels" epLabelValues = apiPrefix + "/label/:name/values" epSeries = apiPrefix + "/series" @@ -239,6 +240,8 @@ type API interface { Query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, error) // QueryRange performs a query for the given range. QueryRange(ctx context.Context, query string, r Range) (model.Value, Warnings, error) + // QueryExemplars performs a query for exemplars by the given query and time range. + QueryExemplars(ctx context.Context, query string, startTime time.Time, endTime time.Time) ([]ExemplarQueryResult, error) // Buildinfo returns various build information properties about the Prometheus server Buildinfo(ctx context.Context) (BuildinfoResult, error) // Runtimeinfo returns the various runtime information properties about the Prometheus server. @@ -588,6 +591,18 @@ func (qr *queryResult) UnmarshalJSON(b []byte) error { return err } +// Exemplar is additional information associated with a time series. +type Exemplar struct { + Labels model.LabelSet `json:"labels"` + Value model.SampleValue `json:"value"` + Timestamp model.Time `json:"timestamp"` +} + +type ExemplarQueryResult struct { + SeriesLabels model.LabelSet `json:"seriesLabels"` + Exemplars []Exemplar `json:"exemplars"` +} + // NewAPI returns a new API for the client. // // It is safe to use the returned API from multiple goroutines. @@ -967,7 +982,29 @@ func (h *httpAPI) TSDB(ctx context.Context) (TSDBResult, error) { var res TSDBResult return res, json.Unmarshal(body, &res) +} +func (h *httpAPI) QueryExemplars(ctx context.Context, query string, startTime time.Time, endTime time.Time) ([]ExemplarQueryResult, error) { + u := h.client.URL(epQueryExemplars, nil) + q := u.Query() + + q.Set("query", query) + q.Set("start", formatTime(startTime)) + q.Set("end", formatTime(endTime)) + u.RawQuery = q.Encode() + + 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 + } + + var res []ExemplarQueryResult + return res, json.Unmarshal(body, &res) } // Warnings is an array of non critical errors diff --git a/api/prometheus/v1/api_test.go b/api/prometheus/v1/api_test.go index 3350847..4197d42 100644 --- a/api/prometheus/v1/api_test.go +++ b/api/prometheus/v1/api_test.go @@ -230,6 +230,13 @@ func TestAPIs(t *testing.T) { } } + doQueryExemplars := func(query string, startTime time.Time, endTime time.Time) func() (interface{}, Warnings, error) { + return func() (interface{}, Warnings, error) { + v, err := promAPI.QueryExemplars(context.Background(), query, startTime, endTime) + return v, nil, err + } + } + queryTests := []apiTest{ { do: doQuery("2", testTime), @@ -1190,6 +1197,66 @@ func TestAPIs(t *testing.T) { }, }, }, + + { + do: doQueryExemplars("tns_request_duration_seconds_bucket", testTime.Add(-1*time.Minute), testTime), + reqMethod: "GET", + reqPath: "/api/v1/query_exemplars", + inErr: fmt.Errorf("some error"), + err: fmt.Errorf("some error"), + }, + + { + do: doQueryExemplars("tns_request_duration_seconds_bucket", testTime.Add(-1*time.Minute), testTime), + reqMethod: "GET", + reqPath: "/api/v1/query_exemplars", + inRes: []interface{}{ + map[string]interface{}{ + "seriesLabels": map[string]interface{}{ + "__name__": "tns_request_duration_seconds_bucket", + "instance": "app:80", + "job": "tns/app", + }, + "exemplars": []interface{}{ + map[string]interface{}{ + "labels": map[string]interface{}{ + "traceID": "19fd8c8a33975a23", + }, + "value": "0.003863295", + "timestamp": model.TimeFromUnixNano(testTime.UnixNano()), + }, + map[string]interface{}{ + "labels": map[string]interface{}{ + "traceID": "67f743f07cc786b0", + }, + "value": "0.001535405", + "timestamp": model.TimeFromUnixNano(testTime.UnixNano()), + }, + }, + }, + }, + res: []ExemplarQueryResult{ + { + SeriesLabels: model.LabelSet{ + "__name__": "tns_request_duration_seconds_bucket", + "instance": "app:80", + "job": "tns/app", + }, + Exemplars: []Exemplar{ + { + Labels: model.LabelSet{"traceID": "19fd8c8a33975a23"}, + Value: 0.003863295, + Timestamp: model.TimeFromUnixNano(testTime.UnixNano()), + }, + { + Labels: model.LabelSet{"traceID": "67f743f07cc786b0"}, + Value: 0.001535405, + Timestamp: model.TimeFromUnixNano(testTime.UnixNano()), + }, + }, + }, + }, + }, } var tests []apiTest