mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-24 21:24:05 -08:00
web/api: replace /metrics/names with /label/:name/values endpoint.
This commit is contained in:
parent
535c002f79
commit
75b0b7420e
|
@ -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{}) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue