web/api: replace /metrics/names with /label/:name/values endpoint.

This commit is contained in:
Fabian Reinartz 2015-06-08 21:19:52 +02:00
parent 535c002f79
commit 75b0b7420e
2 changed files with 60 additions and 14 deletions

View file

@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
clientmodel "github.com/prometheus/client_golang/model" clientmodel "github.com/prometheus/client_golang/model"
@ -29,7 +30,8 @@ const (
type errorType string type errorType string
const ( const (
errorTimeout errorType = "timeout" errorNone errorType = ""
errorTimeout = "timeout"
errorCanceled = "canceled" errorCanceled = "canceled"
errorExec = "execution" errorExec = "execution"
errorBadData = "bad_data" errorBadData = "bad_data"
@ -56,6 +58,8 @@ type response struct {
type API struct { type API struct {
Storage local.Storage Storage local.Storage
QueryEngine *promql.Engine QueryEngine *promql.Engine
context func(r *http.Request) context.Context
} }
// Enables cross-site script calls. // 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. // Register the API's endpoints in the given router.
func (api *API) Register(r *route.Router) { func (api *API) Register(r *route.Router) {
if api.context == nil {
api.context = route.Context
}
instr := func(name string, f apiFunc) http.HandlerFunc { instr := func(name string, f apiFunc) http.HandlerFunc {
return prometheus.InstrumentHandlerFunc(name, func(w http.ResponseWriter, r *http.Request) { return prometheus.InstrumentHandlerFunc(name, func(w http.ResponseWriter, r *http.Request) {
setCORS(w) setCORS(w)
@ -84,7 +92,7 @@ func (api *API) Register(r *route.Router) {
r.Get("/query", instr("query", api.query)) r.Get("/query", instr("query", api.query))
r.Get("/query_range", instr("query_range", api.queryRange)) 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 { type queryData struct {
@ -154,11 +162,16 @@ func (api *API) queryRange(r *http.Request) (interface{}, *apiError) {
}, nil }, nil
} }
func (api *API) metricNames(r *http.Request) (interface{}, *apiError) { func (api *API) labelValues(r *http.Request) (interface{}, *apiError) {
metricNames := api.Storage.LabelValuesForLabelName(clientmodel.MetricNameLabel) name := route.Param(api.context(r), "name")
sort.Sort(metricNames)
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{}) { func respond(w http.ResponseWriter, data interface{}) {

View file

@ -11,9 +11,12 @@ import (
"testing" "testing"
"time" "time"
"golang.org/x/net/context"
clientmodel "github.com/prometheus/client_golang/model" clientmodel "github.com/prometheus/client_golang/model"
"github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/util/route"
) )
func TestEndpoints(t *testing.T) { func TestEndpoints(t *testing.T) {
@ -40,6 +43,7 @@ func TestEndpoints(t *testing.T) {
start := clientmodel.Timestamp(0) start := clientmodel.Timestamp(0)
var tests = []struct { var tests = []struct {
endpoint apiFunc endpoint apiFunc
params map[string]string
query url.Values query url.Values
response interface{} response interface{}
errType errorType 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{ response: clientmodel.LabelValues{
"test_metric1", "test_metric1",
"test_metric2", "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 { 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) req, err := http.NewRequest("ANY", fmt.Sprintf("http://example.com?%s", test.query.Encode()), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
resp, apierr := test.endpoint(req) resp, apiErr := test.endpoint(req)
if apierr != nil { if apiErr != nil {
if test.errType == "" { if test.errType == errorNone {
t.Fatalf("Unexpected error: %s", apierr) t.Fatalf("Unexpected error: %s", apiErr)
} }
if test.errType != apierr.typ { if test.errType != apiErr.typ {
t.Fatalf("Expected error of type %q but got type %q", test.errType, apierr.typ) t.Fatalf("Expected error of type %q but got type %q", test.errType, apiErr.typ)
} }
continue continue
} }
if apierr == nil && test.errType != "" { if apiErr == nil && test.errType != errorNone {
t.Fatalf("Expected error of type %q but got none", test.errType) t.Fatalf("Expected error of type %q but got none", test.errType)
} }
if !reflect.DeepEqual(resp, test.response) { if !reflect.DeepEqual(resp, test.response) {