Merge pull request #781 from prometheus/fabxc/api-v1-ext

Replace /metrics/names with /label/:name/values endpoint.
This commit is contained in:
Fabian Reinartz 2015-06-08 23:13:49 +02:00
commit db4df06414
3 changed files with 65 additions and 14 deletions

View File

@ -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) {

View File

@ -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{}) {

View File

@ -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) {