mirror of
https://github.com/prometheus/prometheus
synced 2024-12-26 16:43:21 +00:00
Merge pull request #781 from prometheus/fabxc/api-v1-ext
Replace /metrics/names with /label/:name/values endpoint.
This commit is contained in:
commit
db4df06414
@ -27,6 +27,11 @@ func Param(ctx context.Context, p string) string {
|
||||
return ctx.Value(param(p)).(string)
|
||||
}
|
||||
|
||||
// WithParam returns a new context with param p set to v.
|
||||
func WithParam(ctx context.Context, p, v string) context.Context {
|
||||
return context.WithValue(ctx, param(p), v)
|
||||
}
|
||||
|
||||
// handle turns a Handle into httprouter.Handle
|
||||
func handle(h http.HandlerFunc) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
@ -29,7 +30,8 @@ const (
|
||||
type errorType string
|
||||
|
||||
const (
|
||||
errorTimeout errorType = "timeout"
|
||||
errorNone errorType = ""
|
||||
errorTimeout = "timeout"
|
||||
errorCanceled = "canceled"
|
||||
errorExec = "execution"
|
||||
errorBadData = "bad_data"
|
||||
@ -56,6 +58,8 @@ type response struct {
|
||||
type API struct {
|
||||
Storage local.Storage
|
||||
QueryEngine *promql.Engine
|
||||
|
||||
context func(r *http.Request) context.Context
|
||||
}
|
||||
|
||||
// Enables cross-site script calls.
|
||||
@ -70,6 +74,10 @@ type apiFunc func(r *http.Request) (interface{}, *apiError)
|
||||
|
||||
// Register the API's endpoints in the given router.
|
||||
func (api *API) Register(r *route.Router) {
|
||||
if api.context == nil {
|
||||
api.context = route.Context
|
||||
}
|
||||
|
||||
instr := func(name string, f apiFunc) http.HandlerFunc {
|
||||
return prometheus.InstrumentHandlerFunc(name, func(w http.ResponseWriter, r *http.Request) {
|
||||
setCORS(w)
|
||||
@ -84,7 +92,7 @@ func (api *API) Register(r *route.Router) {
|
||||
r.Get("/query", instr("query", api.query))
|
||||
r.Get("/query_range", instr("query_range", api.queryRange))
|
||||
|
||||
r.Get("/metrics/names", instr("metric_names", api.metricNames))
|
||||
r.Get("/label/:name/values", instr("label_values", api.labelValues))
|
||||
}
|
||||
|
||||
type queryData struct {
|
||||
@ -154,11 +162,16 @@ func (api *API) queryRange(r *http.Request) (interface{}, *apiError) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) metricNames(r *http.Request) (interface{}, *apiError) {
|
||||
metricNames := api.Storage.LabelValuesForLabelName(clientmodel.MetricNameLabel)
|
||||
sort.Sort(metricNames)
|
||||
func (api *API) labelValues(r *http.Request) (interface{}, *apiError) {
|
||||
name := route.Param(api.context(r), "name")
|
||||
|
||||
return metricNames, nil
|
||||
if !clientmodel.LabelNameRE.MatchString(name) {
|
||||
return nil, &apiError{errorBadData, fmt.Errorf("invalid label name: %q", name)}
|
||||
}
|
||||
vals := api.Storage.LabelValuesForLabelName(clientmodel.LabelName(name))
|
||||
sort.Sort(vals)
|
||||
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
func respond(w http.ResponseWriter, data interface{}) {
|
||||
|
@ -11,9 +11,12 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/util/route"
|
||||
)
|
||||
|
||||
func TestEndpoints(t *testing.T) {
|
||||
@ -40,6 +43,7 @@ func TestEndpoints(t *testing.T) {
|
||||
start := clientmodel.Timestamp(0)
|
||||
var tests = []struct {
|
||||
endpoint apiFunc
|
||||
params map[string]string
|
||||
query url.Values
|
||||
response interface{}
|
||||
errType errorType
|
||||
@ -87,30 +91,59 @@ func TestEndpoints(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
endpoint: api.metricNames,
|
||||
endpoint: api.labelValues,
|
||||
params: map[string]string{
|
||||
"name": "__name__",
|
||||
},
|
||||
response: clientmodel.LabelValues{
|
||||
"test_metric1",
|
||||
"test_metric2",
|
||||
},
|
||||
},
|
||||
{
|
||||
endpoint: api.labelValues,
|
||||
params: map[string]string{
|
||||
"name": "not!!!allowed",
|
||||
},
|
||||
errType: errorBadData,
|
||||
},
|
||||
{
|
||||
endpoint: api.labelValues,
|
||||
params: map[string]string{
|
||||
"name": "foo",
|
||||
},
|
||||
response: clientmodel.LabelValues{
|
||||
"bar",
|
||||
"boo",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Build a context with the correct request params.
|
||||
ctx := context.Background()
|
||||
for p, v := range test.params {
|
||||
ctx = route.WithParam(ctx, p, v)
|
||||
}
|
||||
api.context = func(r *http.Request) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("ANY", fmt.Sprintf("http://example.com?%s", test.query.Encode()), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, apierr := test.endpoint(req)
|
||||
if apierr != nil {
|
||||
if test.errType == "" {
|
||||
t.Fatalf("Unexpected error: %s", apierr)
|
||||
resp, apiErr := test.endpoint(req)
|
||||
if apiErr != nil {
|
||||
if test.errType == errorNone {
|
||||
t.Fatalf("Unexpected error: %s", apiErr)
|
||||
}
|
||||
if test.errType != apierr.typ {
|
||||
t.Fatalf("Expected error of type %q but got type %q", test.errType, apierr.typ)
|
||||
if test.errType != apiErr.typ {
|
||||
t.Fatalf("Expected error of type %q but got type %q", test.errType, apiErr.typ)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if apierr == nil && test.errType != "" {
|
||||
if apiErr == nil && test.errType != errorNone {
|
||||
t.Fatalf("Expected error of type %q but got none", test.errType)
|
||||
}
|
||||
if !reflect.DeepEqual(resp, test.response) {
|
||||
|
Loading…
Reference in New Issue
Block a user